satori-python-server 0.16.7__tar.gz → 0.17.1__tar.gz

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.
@@ -1,5 +1,5 @@
1
1
  includes = ["src/satori/server"]
2
- raw-dependencies = ["satori-python-core >= 0.16.3"]
2
+ raw-dependencies = ["satori-python-core >= 0.17.0"]
3
3
 
4
4
  [project]
5
5
  name = "satori-python-server"
@@ -10,15 +10,15 @@ authors = [
10
10
  dependencies = [
11
11
  "aiohttp",
12
12
  "launart",
13
- "graia-amnesia",
14
- "starlette",
15
- "uvicorn[standard]",
13
+ "graia-amnesia[uvicorn]",
14
+ "starlette[python-multipart]",
15
+ "websockets",
16
16
  "python-multipart",
17
17
  ]
18
18
  description = "Satori Protocol SDK for python, specify server part"
19
19
  license = {text = "MIT"}
20
20
  readme = "README.md"
21
- requires-python = ">=3.9"
21
+ requires-python = ">=3.10,<4.0"
22
22
  classifiers = [
23
23
  "Typing :: Typed",
24
24
  "Development Status :: 4 - Beta",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-server
3
- Version: 0.16.7
3
+ Version: 0.17.1
4
4
  Summary: Satori Protocol SDK for python, specify server part
5
5
  Home-page: https://github.com/RF-Tar-Railt/satori-python
6
6
  Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
@@ -16,14 +16,14 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Operating System :: OS Independent
17
17
  Project-URL: Homepage, https://github.com/RF-Tar-Railt/satori-python
18
18
  Project-URL: Repository, https://github.com/RF-Tar-Railt/satori-python
19
- Requires-Python: >=3.9
19
+ Requires-Python: <4.0,>=3.10
20
20
  Requires-Dist: aiohttp>=3.9.3
21
21
  Requires-Dist: launart>=0.8.2
22
- Requires-Dist: graia-amnesia>=0.9.0
22
+ Requires-Dist: graia-amnesia[uvicorn]<0.12.0,>=0.11.0
23
23
  Requires-Dist: starlette[python-multipart]>=0.37.2
24
- Requires-Dist: uvicorn[standard]>=0.28.0
24
+ Requires-Dist: websockets>=15.0.1
25
25
  Requires-Dist: python-multipart>=0.0.9
26
- Requires-Dist: satori-python-core>=0.16.3
26
+ Requires-Dist: satori-python-core>=0.17.0
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # satori-python
@@ -7,15 +7,15 @@ authors = [
7
7
  dependencies = [
8
8
  "aiohttp>=3.9.3",
9
9
  "launart>=0.8.2",
10
- "graia-amnesia>=0.9.0",
10
+ "graia-amnesia[uvicorn]<0.12.0,>=0.11.0",
11
11
  "starlette[python-multipart]>=0.37.2",
12
- "uvicorn[standard]>=0.28.0",
12
+ "websockets>=15.0.1",
13
13
  "python-multipart>=0.0.9",
14
- "satori-python-core >= 0.16.3",
14
+ "satori-python-core >= 0.17.0",
15
15
  ]
16
16
  description = "Satori Protocol SDK for python, specify server part"
17
17
  readme = "README.md"
18
- requires-python = ">=3.9"
18
+ requires-python = ">=3.10,<4.0"
19
19
  classifiers = [
20
20
  "Typing :: Typed",
21
21
  "Development Status :: 4 - Beta",
@@ -27,7 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.12",
28
28
  "Operating System :: OS Independent",
29
29
  ]
30
- version = "0.16.7"
30
+ version = "0.17.1"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -43,7 +43,7 @@ requires = [
43
43
  ]
44
44
  build-backend = "mina.backend"
45
45
 
46
- [tool.pdm.dev-dependencies]
46
+ [dependency-groups]
47
47
  dev = [
48
48
  "isort>=5.13.2",
49
49
  "black>=24.4.0",
@@ -53,6 +53,7 @@ dev = [
53
53
  "mina-build<0.6,>=0.5.1",
54
54
  "pdm-mina>=0.3.2",
55
55
  "nonechat<0.7.0,>=0.6.0",
56
+ "uvicorn[standard]>=0.35.0",
56
57
  ]
57
58
 
58
59
  [tool.pdm.build]
@@ -76,21 +77,21 @@ source = "file"
76
77
  path = "src/satori/__init__.py"
77
78
 
78
79
  [tool.black]
79
- line-length = 110
80
+ line-length = 120
80
81
  include = "\\.pyi?$"
81
82
  extend-exclude = ""
82
83
 
83
84
  [tool.isort]
84
85
  profile = "black"
85
- line_length = 110
86
+ line_length = 120
86
87
  skip_gitignore = true
87
88
  extra_standard_library = [
88
89
  "typing_extensions",
89
90
  ]
90
91
 
91
92
  [tool.ruff]
92
- line-length = 110
93
- target-version = "py39"
93
+ line-length = 120
94
+ target-version = "py310"
94
95
  exclude = [
95
96
  "exam.py",
96
97
  ]
@@ -110,12 +111,12 @@ ignore = [
110
111
  "F403",
111
112
  "F405",
112
113
  "C901",
113
- "UP037",
114
+ "UP038",
114
115
  ]
115
116
 
116
117
  [tool.pyright]
117
118
  pythonPlatform = "All"
118
- pythonVersion = "3.9"
119
+ pythonVersion = "3.10"
119
120
  typeCheckingMode = "basic"
120
121
  reportShadowedImports = false
121
122
  disableBytesTypePromotions = true
@@ -8,18 +8,20 @@ import secrets
8
8
  import signal
9
9
  import threading
10
10
  import urllib.parse
11
- from collections.abc import Awaitable, Iterable
11
+ from collections.abc import Awaitable, Callable, Iterable
12
12
  from contextlib import suppress
13
13
  from itertools import chain
14
14
  from pathlib import Path
15
15
  from tempfile import TemporaryDirectory
16
16
  from traceback import print_exc
17
- from typing import Any, Callable, TypeVar
17
+ from typing import Any, TypeVar
18
18
 
19
19
  import aiohttp
20
+ from aiohttp import ClientTimeout
20
21
  from creart import it
21
22
  from graia.amnesia.builtins.aiohttp import AiohttpClientService
22
- from graia.amnesia.builtins.asgi import UvicornASGIService, asgitypes
23
+ from graia.amnesia.builtins.asgi import asgitypes
24
+ from graia.amnesia.builtins.asgi.uvicorn import UvicornASGIService, UvicornOptions
23
25
  from launart import Launart, Service, any_completed
24
26
  from loguru import logger
25
27
  from starlette.applications import Starlette
@@ -39,6 +41,7 @@ from yarl import URL
39
41
 
40
42
  from satori.const import Api
41
43
  from satori.model import Event, Meta, ModelBase, Opcode
44
+ from satori.utils import decode
42
45
 
43
46
  from .. import EventType
44
47
  from .adapter import Adapter as Adapter
@@ -57,9 +60,7 @@ _T_ws_endpoint = TypeVar("_T_ws_endpoint", bound=Callable[[WebSocket], Awaitable
57
60
  StarletteResponse = Response
58
61
 
59
62
 
60
- async def _request_handler(
61
- action: str, request: StarletteRequest, func: RouteCall, platform: str, self_id: str
62
- ):
63
+ async def _request_handler(action: str, request: StarletteRequest, func: RouteCall, platform: str, self_id: str):
63
64
  if action == Api.UPLOAD_CREATE.value:
64
65
  async with request.form() as form:
65
66
  res = await func(
@@ -77,7 +78,7 @@ async def _request_handler(
77
78
  Request(
78
79
  request,
79
80
  action,
80
- await request.json(),
81
+ decode(await request.body()),
81
82
  platform=platform,
82
83
  self_id=self_id,
83
84
  )
@@ -115,6 +116,8 @@ class Server(Service, RouterMixin):
115
116
  version: str = "v1",
116
117
  token: str | None = None,
117
118
  webhooks: list[WebhookEndpoint] | None = None,
119
+ uvicorn_options: UvicornOptions | None = None,
120
+ *,
118
121
  stream_threshold: int = 16 * 1024 * 1024,
119
122
  stream_chunk_size: int = 64 * 1024,
120
123
  ):
@@ -123,8 +126,11 @@ class Server(Service, RouterMixin):
123
126
  self.port = port
124
127
  self.version = version
125
128
  self.path = path
129
+ self.uvicorn_options = uvicorn_options
126
130
  if self.path and not self.path.startswith("/"):
127
131
  self.path = f"/{self.path}"
132
+ if (self.host == "0.0.0.0" or self.host == "::") and not token:
133
+ raise ValueError("Token is required when the server is exposed to the public network")
128
134
  self.token = token
129
135
  self._adapters = []
130
136
  self.providers = []
@@ -138,6 +144,7 @@ class Server(Service, RouterMixin):
138
144
  self.stream_chunk_size = stream_chunk_size
139
145
  self.resources: dict[str, Path] = {}
140
146
  self.app = Starlette()
147
+ self.asgi_service = UvicornASGIService(self.host, self.port, options=self.uvicorn_options)
141
148
  super().__init__()
142
149
 
143
150
  def replace_app(self, app: ASGIApp | asgitypes.ASGI3Application):
@@ -161,9 +168,7 @@ class Server(Service, RouterMixin):
161
168
  """
162
169
 
163
170
  def wrapper(endpoint: _T_endpoint, /) -> _T_endpoint:
164
- self.app.add_route(
165
- path, endpoint, methods=methods, name=name, include_in_schema=include_in_schema
166
- )
171
+ self.app.add_route(path, endpoint, methods=methods, name=name, include_in_schema=include_in_schema)
167
172
  return endpoint
168
173
 
169
174
  return wrapper
@@ -206,7 +211,7 @@ class Server(Service, RouterMixin):
206
211
  """在指定路径挂载静态文件"""
207
212
  self.resources[route_path] = file
208
213
 
209
- async def event_callback(self, event: Event):
214
+ async def post(self, event: Event):
210
215
  event.sn = self._sequence
211
216
  self._event_cache.append(event)
212
217
  self._sequence += 1
@@ -230,6 +235,7 @@ class Server(Service, RouterMixin):
230
235
  "Satori-OpCode": str(Opcode.EVENT.value),
231
236
  },
232
237
  json=event.dump(),
238
+ timeout=ClientTimeout(hook.timeout or 300),
233
239
  ) as resp:
234
240
  resp.raise_for_status()
235
241
  except Exception as e:
@@ -239,7 +245,7 @@ class Server(Service, RouterMixin):
239
245
  async def websocket_server_handler(self, ws: WebSocket):
240
246
  await ws.accept()
241
247
  connection = WebsocketConnection(ws)
242
- identity = await ws.receive_json()
248
+ identity = decode(await ws.receive_text())
243
249
  if not isinstance(identity, dict) or identity.get("op") != Opcode.IDENTIFY:
244
250
  return await ws.close(code=3000, reason="Unauthorized")
245
251
  body = identity["body"]
@@ -258,7 +264,7 @@ class Server(Service, RouterMixin):
258
264
  {"op": Opcode.READY, "body": {"logins": [lo.dump() for lo in logins], "proxy_urls": proxy_urls}}
259
265
  )
260
266
  self.connections.append(connection)
261
- logger.debug(f"New connection: {id(connection)}")
267
+ logger.debug(f"New connection: {id(connection):x}")
262
268
  heartbeat_task = asyncio.create_task(connection.heartbeat())
263
269
  close_task = asyncio.create_task(connection.close_signal.wait())
264
270
  try:
@@ -275,7 +281,7 @@ class Server(Service, RouterMixin):
275
281
  await any_completed(heartbeat_task, close_task)
276
282
  finally:
277
283
  await connection.connection_closed()
278
- logger.debug(f"Connection closed: {id(connection)}")
284
+ logger.debug(f"Connection closed: {id(connection):x}")
279
285
  heartbeat_task.cancel()
280
286
  close_task.cancel()
281
287
  self.connections.remove(connection)
@@ -311,8 +317,7 @@ class Server(Service, RouterMixin):
311
317
  resp = await self.fetch_proxy(url, request)
312
318
  # if content size > stream_limit, use streaming response
313
319
  if (
314
- isinstance(resp, (PlainTextResponse, HTMLResponse, JSONResponse))
315
- or resp.__class__ is Response
320
+ isinstance(resp, (PlainTextResponse, HTMLResponse, JSONResponse)) or resp.__class__ is Response
316
321
  ) and len(resp.body) > self.stream_threshold:
317
322
 
318
323
  async def iter_content(body: bytes):
@@ -402,7 +407,7 @@ class Server(Service, RouterMixin):
402
407
  return JSONResponse(content=Meta(logins, proxy_urls).dump())
403
408
 
404
409
  async def webhook_create_handler(self, request: StarletteRequest):
405
- body = await request.json()
410
+ body = decode(await request.body())
406
411
  url = body["url"]
407
412
  token = body.get("token")
408
413
  self.webhooks.append(WebhookEndpoint(url, token))
@@ -422,7 +427,7 @@ class Server(Service, RouterMixin):
422
427
  return Response()
423
428
 
424
429
  async def webhook_delete_handler(self, request: StarletteRequest):
425
- body = await request.json()
430
+ body = decode(await request.body())
426
431
  url = body["url"]
427
432
  for endpoint in self.webhooks:
428
433
  if endpoint.url == url:
@@ -438,7 +443,6 @@ class Server(Service, RouterMixin):
438
443
  self.routes[Api.UPLOAD_CREATE.value] = self._default_upload_create_handler
439
444
 
440
445
  async with self.stage("preparing"):
441
- asgi_service = manager.get_component(UvicornASGIService)
442
446
  self.app.routes.extend(
443
447
  [
444
448
  *chain.from_iterable(ada.get_routes() for ada in self._adapters),
@@ -472,11 +476,13 @@ class Server(Service, RouterMixin):
472
476
  )
473
477
  for path, file in self.resources.items():
474
478
  self.app.mount(path, StaticFiles(directory=file.parent, html=file.suffix == ".html"))
475
- asgi_service.middleware.mounts[""] = self.app # type: ignore
479
+ self.asgi_service.middleware.mounts[""] = self.app # type: ignore
476
480
 
477
481
  async def event_task(_provider: Provider):
478
- async for event in _provider.publisher():
479
- await self.event_callback(event)
482
+ async for event in _provider.publisher(): # type: ignore
483
+ await self.post(event)
484
+
485
+ event_tasks = [event_task(_provider) for _provider in self.providers if hasattr(_provider, "publisher")]
480
486
 
481
487
  async with self.stage("blocking"):
482
488
  proxy_urls = []
@@ -491,17 +497,18 @@ class Server(Service, RouterMixin):
491
497
  "Satori-OpCode": str(Opcode.META.value),
492
498
  },
493
499
  json={"proxy_urls": proxy_urls},
500
+ timeout=ClientTimeout(hook.timeout or 300),
494
501
  ) as resp:
495
502
  resp.raise_for_status()
496
503
  await any_completed(
497
504
  manager.status.wait_for_sigexit(),
498
- *(event_task(provider) for provider in self.providers),
505
+ *event_tasks,
499
506
  *(_adapter.status.wait_for("blocking-completed") for _adapter in self._adapters),
500
507
  )
501
508
 
502
509
  async with self.stage("cleanup"):
503
510
  with suppress(KeyError):
504
- del asgi_service.middleware.mounts[""]
511
+ del self.asgi_service.middleware.mounts[""]
505
512
  await self.session.close()
506
513
  self._tempdir.cleanup()
507
514
 
@@ -514,7 +521,7 @@ class Server(Service, RouterMixin):
514
521
  ):
515
522
  if manager is None:
516
523
  manager = it(Launart)
517
- manager.add_component(UvicornASGIService(self.host, self.port))
524
+ manager.add_component(self.asgi_service)
518
525
  manager.add_component(self)
519
526
  with suppress(ValueError):
520
527
  manager.add_component(AiohttpClientService())
@@ -527,7 +534,10 @@ class Server(Service, RouterMixin):
527
534
  ):
528
535
  if manager is None:
529
536
  manager = it(Launart)
537
+ manager.add_component(self.asgi_service)
530
538
  manager.add_component(self)
539
+ with suppress(ValueError):
540
+ manager.add_component(AiohttpClientService())
531
541
  handled_signals: dict[signal.Signals, Any] = {}
532
542
  launch_task = asyncio.create_task(manager.launch(), name="amnesia-launch")
533
543
  signal_handler = functools.partial(manager._on_sys_signal, main_task=launch_task)
@@ -1,12 +1,11 @@
1
1
  from abc import abstractmethod
2
- from collections.abc import AsyncIterator
3
- from typing import TYPE_CHECKING, Optional, Union
2
+ from typing import TYPE_CHECKING, Union
4
3
 
5
4
  from launart import Service
6
5
  from starlette.responses import Response
7
6
  from starlette.routing import BaseRoute
8
7
 
9
- from satori.model import Event, Login, LoginPartial
8
+ from satori.model import Login, LoginPartial
10
9
 
11
10
  from .model import Request
12
11
  from .route import RouterMixin
@@ -16,7 +15,7 @@ if TYPE_CHECKING:
16
15
  from . import Server
17
16
 
18
17
 
19
- LoginType = Union[Login, LoginPartial]
18
+ LoginType = Union[Login, LoginPartial] # noqa: UP007
20
19
 
21
20
 
22
21
  class Adapter(Service, RouterMixin):
@@ -25,9 +24,6 @@ class Adapter(Service, RouterMixin):
25
24
  @abstractmethod
26
25
  def get_platform(self) -> str: ...
27
26
 
28
- @abstractmethod
29
- def publisher(self) -> AsyncIterator[Event]: ...
30
-
31
27
  @abstractmethod
32
28
  def ensure(self, platform: str, self_id: str) -> bool: ...
33
29
 
@@ -38,7 +34,7 @@ class Adapter(Service, RouterMixin):
38
34
  @abstractmethod
39
35
  async def handle_internal(self, request: Request, path: str) -> Response: ...
40
36
 
41
- async def handle_proxied(self, prefix: str, url: str) -> Optional[Response]:
37
+ async def handle_proxied(self, prefix: str, url: str) -> Response | None:
42
38
  async with self.server.session.get(url, ssl=ctx) as resp:
43
39
  return Response(await resp.read())
44
40
 
@@ -6,6 +6,7 @@ from loguru import logger
6
6
  from starlette.websockets import WebSocket, WebSocketDisconnect
7
7
 
8
8
  from satori.model import Opcode
9
+ from satori.utils import decode, encode
9
10
 
10
11
 
11
12
  class WebsocketConnection:
@@ -22,12 +23,13 @@ class WebsocketConnection:
22
23
  async def heartbeat(self):
23
24
  while True:
24
25
  try:
25
- msg = await asyncio.wait_for(self.connection.receive_json(), timeout=12)
26
+ msg = await asyncio.wait_for(self.connection.receive_text(), timeout=12)
27
+ msg = decode(msg)
26
28
  if not isinstance(msg, dict) or msg.get("op") != Opcode.PING:
27
29
  continue
28
- await self.connection.send_json({"op": Opcode.PONG})
30
+ await self.connection.send_text(encode({"op": Opcode.PONG}))
29
31
  except asyncio.TimeoutError:
30
- logger.warning(f"Connection {id(self)} heartbeat timeout, closing connection.")
32
+ logger.warning(f"Connection {id(self):x} heartbeat timeout, closing connection.")
31
33
  await self.connection.close()
32
34
  await self.connection_closed()
33
35
  break
@@ -41,4 +43,4 @@ class WebsocketConnection:
41
43
  return
42
44
 
43
45
  async def send(self, payload: dict) -> None:
44
- return await self.connection.send_json(payload)
46
+ return await self.connection.send_text(encode(payload))
@@ -1,17 +1,15 @@
1
- from collections.abc import AsyncIterator
2
1
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Generic, Optional, Protocol, TypeVar, Union, runtime_checkable
2
+ from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar, runtime_checkable
4
3
 
5
4
  from starlette.requests import Request as StarletteRequest
6
5
  from starlette.responses import Response
7
6
 
8
7
  from satori.const import Api
9
- from satori.model import Event, Login
8
+ from satori.model import Login
10
9
 
11
10
  if TYPE_CHECKING:
12
11
  from .route import RouteCall
13
12
 
14
- JsonType = Union[list, dict, str, int, bool, float, None]
15
13
  TA = TypeVar("TA", str, Api)
16
14
  TP = TypeVar("TP")
17
15
 
@@ -27,8 +25,6 @@ class Request(Generic[TP]):
27
25
 
28
26
  @runtime_checkable
29
27
  class Provider(Protocol):
30
- def publisher(self) -> AsyncIterator[Event]: ...
31
-
32
28
  async def get_logins(self) -> list[Login]: ...
33
29
 
34
30
  @staticmethod
@@ -38,7 +34,7 @@ class Provider(Protocol):
38
34
 
39
35
  async def handle_internal(self, request: Request, path: str) -> Response: ...
40
36
 
41
- async def handle_proxied(self, prefix: str, url: str) -> Optional[Response]: ...
37
+ async def handle_proxied(self, prefix: str, url: str) -> Response | None: ...
42
38
 
43
39
 
44
40
  @runtime_checkable
@@ -49,4 +45,5 @@ class Router(Protocol):
49
45
  @dataclass
50
46
  class WebhookEndpoint:
51
47
  url: str
52
- token: Optional[str] = None
48
+ token: str | None = None
49
+ timeout: float | None = None
@@ -1,6 +1,6 @@
1
- from collections.abc import Awaitable
2
- from typing import Any, Callable, Literal, Protocol, TypeVar, Union, overload
3
- from typing_extensions import NotRequired, TypeAlias, TypedDict
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import Any, Literal, Protocol, TypeAlias, TypeVar, overload
3
+ from typing_extensions import NotRequired, TypedDict
4
4
 
5
5
  from starlette.datastructures import FormData
6
6
 
@@ -30,9 +30,7 @@ class RouteCall(Protocol[T, R]):
30
30
  def __call__(self, request: Request[T]) -> Awaitable[R]: ...
31
31
 
32
32
 
33
- INTERAL: TypeAlias = RouteCall[
34
- Any, Union[ModelBase, list[ModelBase], dict[str, Any], list[dict[str, Any]], None]
35
- ]
33
+ INTERAL: TypeAlias = RouteCall[Any, ModelBase | list[ModelBase] | dict[str, Any] | list[dict[str, Any]] | None]
36
34
 
37
35
 
38
36
  class MessageParam(TypedDict):
@@ -40,7 +38,7 @@ class MessageParam(TypedDict):
40
38
  content: str
41
39
 
42
40
 
43
- MESSAGE_CREATE: TypeAlias = RouteCall[MessageParam, Union[list[MessageObject], list[dict[str, Any]]]]
41
+ MESSAGE_CREATE: TypeAlias = RouteCall[MessageParam, list[MessageObject] | list[dict[str, Any]]]
44
42
 
45
43
 
46
44
  class MessageOpParam(TypedDict):
@@ -48,7 +46,7 @@ class MessageOpParam(TypedDict):
48
46
  message_id: str
49
47
 
50
48
 
51
- MESSAGE_GET: TypeAlias = RouteCall[MessageOpParam, Union[MessageObject, dict[str, Any]]]
49
+ MESSAGE_GET: TypeAlias = RouteCall[MessageOpParam, MessageObject | dict[str, Any]]
52
50
  MESSAGE_DELETE: TypeAlias = RouteCall[MessageOpParam, None]
53
51
 
54
52
 
@@ -69,14 +67,14 @@ class MessageListParam(TypedDict):
69
67
  order: NotRequired[Order]
70
68
 
71
69
 
72
- MESSAGE_LIST: TypeAlias = RouteCall[MessageListParam, Union[PageDequeResult[MessageObject], dict[str, Any]]]
70
+ MESSAGE_LIST: TypeAlias = RouteCall[MessageListParam, PageDequeResult[MessageObject] | dict[str, Any]]
73
71
 
74
72
 
75
73
  class ChannelParam(TypedDict):
76
74
  channel_id: str
77
75
 
78
76
 
79
- CHANNEL_GET: TypeAlias = RouteCall[ChannelParam, Union[Channel, dict[str, Any]]]
77
+ CHANNEL_GET: TypeAlias = RouteCall[ChannelParam, Channel | dict[str, Any]]
80
78
  CHANNEL_DELETE: TypeAlias = RouteCall[ChannelParam, None]
81
79
 
82
80
 
@@ -85,7 +83,7 @@ class ChannelListParam(TypedDict):
85
83
  next: NotRequired[str]
86
84
 
87
85
 
88
- CHANNEL_LIST: TypeAlias = RouteCall[ChannelListParam, Union[PageResult[Channel], dict[str, Any]]]
86
+ CHANNEL_LIST: TypeAlias = RouteCall[ChannelListParam, PageResult[Channel] | dict[str, Any]]
89
87
 
90
88
 
91
89
  class ChannelCreateParam(TypedDict):
@@ -93,7 +91,7 @@ class ChannelCreateParam(TypedDict):
93
91
  data: dict
94
92
 
95
93
 
96
- CHANNEL_CREATE: TypeAlias = RouteCall[ChannelCreateParam, Union[Channel, dict[str, Any]]]
94
+ CHANNEL_CREATE: TypeAlias = RouteCall[ChannelCreateParam, Channel | dict[str, Any]]
97
95
 
98
96
 
99
97
  class ChannelUpdateParam(TypedDict):
@@ -117,21 +115,21 @@ class UserChannelCreateParam(TypedDict):
117
115
  guild_id: NotRequired[str]
118
116
 
119
117
 
120
- ROUTE_USER_CHANNEL_CREATE: TypeAlias = RouteCall[UserChannelCreateParam, Union[Channel, dict[str, Any]]]
118
+ ROUTE_USER_CHANNEL_CREATE: TypeAlias = RouteCall[UserChannelCreateParam, Channel | dict[str, Any]]
121
119
 
122
120
 
123
121
  class GuildGetParam(TypedDict):
124
122
  guild_id: str
125
123
 
126
124
 
127
- GUILD_GET: TypeAlias = RouteCall[GuildGetParam, Union[Guild, dict[str, Any]]]
125
+ GUILD_GET: TypeAlias = RouteCall[GuildGetParam, Guild | dict[str, Any]]
128
126
 
129
127
 
130
128
  class GuildListParam(TypedDict):
131
129
  next: NotRequired[str]
132
130
 
133
131
 
134
- GUILD_LIST: TypeAlias = RouteCall[GuildListParam, Union[PageResult[Guild], dict[str, Any]]]
132
+ GUILD_LIST: TypeAlias = RouteCall[GuildListParam, PageResult[Guild] | dict[str, Any]]
135
133
 
136
134
 
137
135
  class GuildMemberGetParam(TypedDict):
@@ -139,7 +137,7 @@ class GuildMemberGetParam(TypedDict):
139
137
  user_id: str
140
138
 
141
139
 
142
- GUILD_MEMBER_GET: TypeAlias = RouteCall[GuildMemberGetParam, Union[Member, dict[str, Any]]]
140
+ GUILD_MEMBER_GET: TypeAlias = RouteCall[GuildMemberGetParam, Member | dict[str, Any]]
143
141
 
144
142
 
145
143
  class GuildXXXListParam(TypedDict):
@@ -147,7 +145,7 @@ class GuildXXXListParam(TypedDict):
147
145
  next: NotRequired[str]
148
146
 
149
147
 
150
- GUILD_MEMBER_LIST: TypeAlias = RouteCall[GuildXXXListParam, Union[PageResult[Member], dict[str, Any]]]
148
+ GUILD_MEMBER_LIST: TypeAlias = RouteCall[GuildXXXListParam, PageResult[Member] | dict[str, Any]]
151
149
 
152
150
 
153
151
  class GuildMemberKickParam(TypedDict):
@@ -177,7 +175,7 @@ class GuildMemberRoleParam(TypedDict):
177
175
  GUILD_MEMBER_ROLE_SET: TypeAlias = RouteCall[GuildMemberRoleParam, None]
178
176
  GUILD_MEMBER_ROLE_UNSET: TypeAlias = RouteCall[GuildMemberRoleParam, None]
179
177
 
180
- GUILD_ROLE_LIST: TypeAlias = RouteCall[GuildXXXListParam, Union[PageResult[Role], dict[str, Any]]]
178
+ GUILD_ROLE_LIST: TypeAlias = RouteCall[GuildXXXListParam, PageResult[Role] | dict[str, Any]]
181
179
 
182
180
 
183
181
  class GuildRoleCreateParam(TypedDict):
@@ -185,7 +183,7 @@ class GuildRoleCreateParam(TypedDict):
185
183
  role: dict
186
184
 
187
185
 
188
- GUILD_ROLE_CREATE: TypeAlias = RouteCall[GuildRoleCreateParam, Union[Role, dict[str, Any]]]
186
+ GUILD_ROLE_CREATE: TypeAlias = RouteCall[GuildRoleCreateParam, Role | dict[str, Any]]
189
187
 
190
188
 
191
189
  class GuildRoleUpdateParam(TypedDict):
@@ -240,22 +238,22 @@ class ReactionListParam(TypedDict):
240
238
  next: NotRequired[str]
241
239
 
242
240
 
243
- REACTION_LIST: TypeAlias = RouteCall[ReactionListParam, Union[PageResult[User], dict[str, Any]]]
244
- LOGIN_GET: TypeAlias = RouteCall[Any, Union[Login, dict[str, Any]]]
241
+ REACTION_LIST: TypeAlias = RouteCall[ReactionListParam, PageResult[User] | dict[str, Any]]
242
+ LOGIN_GET: TypeAlias = RouteCall[Any, Login | dict[str, Any]]
245
243
 
246
244
 
247
245
  class UserGetParam(TypedDict):
248
246
  user_id: str
249
247
 
250
248
 
251
- USER_GET: TypeAlias = RouteCall[UserGetParam, Union[User, dict[str, Any]]]
249
+ USER_GET: TypeAlias = RouteCall[UserGetParam, User | dict[str, Any]]
252
250
 
253
251
 
254
252
  class FriendListParam(TypedDict):
255
253
  next: NotRequired[str]
256
254
 
257
255
 
258
- FRIEND_LIST: TypeAlias = RouteCall[FriendListParam, Union[PageResult[User], dict[str, Any]]]
256
+ FRIEND_LIST: TypeAlias = RouteCall[FriendListParam, PageResult[User] | dict[str, Any]]
259
257
 
260
258
 
261
259
  class ApproveParam(TypedDict):
@@ -321,24 +319,16 @@ class RouterMixin:
321
319
  def route(self, path: Literal[Api.GUILD_APPROVE]) -> Callable[[APPROVE], APPROVE]: ...
322
320
 
323
321
  @overload
324
- def route(
325
- self, path: Literal[Api.GUILD_MEMBER_LIST]
326
- ) -> Callable[[GUILD_MEMBER_LIST], GUILD_MEMBER_LIST]: ...
322
+ def route(self, path: Literal[Api.GUILD_MEMBER_LIST]) -> Callable[[GUILD_MEMBER_LIST], GUILD_MEMBER_LIST]: ...
327
323
 
328
324
  @overload
329
- def route(
330
- self, path: Literal[Api.GUILD_MEMBER_GET]
331
- ) -> Callable[[GUILD_MEMBER_GET], GUILD_MEMBER_GET]: ...
325
+ def route(self, path: Literal[Api.GUILD_MEMBER_GET]) -> Callable[[GUILD_MEMBER_GET], GUILD_MEMBER_GET]: ...
332
326
 
333
327
  @overload
334
- def route(
335
- self, path: Literal[Api.GUILD_MEMBER_KICK]
336
- ) -> Callable[[GUILD_MEMBER_KICK], GUILD_MEMBER_KICK]: ...
328
+ def route(self, path: Literal[Api.GUILD_MEMBER_KICK]) -> Callable[[GUILD_MEMBER_KICK], GUILD_MEMBER_KICK]: ...
337
329
 
338
330
  @overload
339
- def route(
340
- self, path: Literal[Api.GUILD_MEMBER_MUTE]
341
- ) -> Callable[[GUILD_MEMBER_MUTE], GUILD_MEMBER_MUTE]: ...
331
+ def route(self, path: Literal[Api.GUILD_MEMBER_MUTE]) -> Callable[[GUILD_MEMBER_MUTE], GUILD_MEMBER_MUTE]: ...
342
332
 
343
333
  @overload
344
334
  def route(self, path: Literal[Api.GUILD_MEMBER_APPROVE]) -> Callable[[APPROVE], APPROVE]: ...
@@ -357,19 +347,13 @@ class RouterMixin:
357
347
  def route(self, path: Literal[Api.GUILD_ROLE_LIST]) -> Callable[[GUILD_ROLE_LIST], GUILD_ROLE_LIST]: ...
358
348
 
359
349
  @overload
360
- def route(
361
- self, path: Literal[Api.GUILD_ROLE_CREATE]
362
- ) -> Callable[[GUILD_ROLE_CREATE], GUILD_ROLE_CREATE]: ...
350
+ def route(self, path: Literal[Api.GUILD_ROLE_CREATE]) -> Callable[[GUILD_ROLE_CREATE], GUILD_ROLE_CREATE]: ...
363
351
 
364
352
  @overload
365
- def route(
366
- self, path: Literal[Api.GUILD_ROLE_UPDATE]
367
- ) -> Callable[[GUILD_ROLE_UPDATE], GUILD_ROLE_UPDATE]: ...
353
+ def route(self, path: Literal[Api.GUILD_ROLE_UPDATE]) -> Callable[[GUILD_ROLE_UPDATE], GUILD_ROLE_UPDATE]: ...
368
354
 
369
355
  @overload
370
- def route(
371
- self, path: Literal[Api.GUILD_ROLE_DELETE]
372
- ) -> Callable[[GUILD_ROLE_DELETE], GUILD_ROLE_DELETE]: ...
356
+ def route(self, path: Literal[Api.GUILD_ROLE_DELETE]) -> Callable[[GUILD_ROLE_DELETE], GUILD_ROLE_DELETE]: ...
373
357
 
374
358
  @overload
375
359
  def route(self, path: Literal[Api.REACTION_CREATE]) -> Callable[[REACTION_CREATE], REACTION_CREATE]: ...
@@ -401,7 +385,7 @@ class RouterMixin:
401
385
  @overload
402
386
  def route(self, path: str) -> Callable[[INTERAL], INTERAL]: ...
403
387
 
404
- def route(self, path: Union[str, Api]) -> Callable[[RouteCall], RouteCall]:
388
+ def route(self, path: str | Api) -> Callable[[RouteCall], RouteCall]:
405
389
  """注册一个 Satori 路由
406
390
 
407
391
  Args:
@@ -33,6 +33,9 @@ if __name__ == "__main__":
33
33
  d.append(0)
34
34
  d.append(1)
35
35
  d.append(2)
36
+ print(d.after(0)) # noqa: T201
36
37
  d.append(3)
37
38
  d.append(4)
38
39
  d.append(5)
40
+ print(d.data) # noqa: T201
41
+ print(d.after(2)) # noqa: T201