satori-python 1.3.6__tar.gz → 1.3.8__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.
Files changed (31) hide show
  1. {satori_python-1.3.6 → satori_python-1.3.8}/PKG-INFO +1 -1
  2. {satori_python-1.3.6 → satori_python-1.3.8}/pyproject.toml +1 -1
  3. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/__init__.py +1 -1
  4. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/__init__.py +1 -4
  5. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/config.py +3 -3
  6. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/element.py +9 -0
  7. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/model.py +3 -3
  8. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/__init__.py +20 -14
  9. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/adapter.py +4 -2
  10. {satori_python-1.3.6 → satori_python-1.3.8}/LICENSE +0 -0
  11. {satori_python-1.3.6 → satori_python-1.3.8}/README.md +0 -0
  12. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/_vendor/fleep.py +0 -0
  13. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/account.py +0 -0
  14. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/account.pyi +0 -0
  15. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/network/__init__.py +0 -0
  16. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/network/base.py +0 -0
  17. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/network/util.py +0 -0
  18. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/network/webhook.py +0 -0
  19. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/network/websocket.py +0 -0
  20. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/client/protocol.py +0 -0
  21. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/const.py +0 -0
  22. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/event.py +0 -0
  23. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/exception.py +0 -0
  24. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/parser.py +0 -0
  25. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/py.typed +0 -0
  26. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/connection.py +0 -0
  27. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/formdata.py +0 -0
  28. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/model.py +0 -0
  29. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/route.py +0 -0
  30. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/server/utils.py +0 -0
  31. {satori_python-1.3.6 → satori_python-1.3.8}/src/satori/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python
3
- Version: 1.3.6
3
+ Version: 1.3.8
4
4
  Summary: Satori Protocol SDK for python
5
5
  Home-page: https://github.com/RF-Tar-Railt/satori-python
6
6
  Author-Email: RF-Tar-Railt <rf_tar_railt@qq.com>
@@ -30,7 +30,7 @@ classifiers = [
30
30
  "Programming Language :: Python :: 3.14",
31
31
  "Operating System :: OS Independent",
32
32
  ]
33
- version = "1.3.6"
33
+ version = "1.3.8"
34
34
 
35
35
  [project.license]
36
36
  text = "MIT"
@@ -45,7 +45,7 @@ from .model import Role as Role
45
45
  from .model import Upload as Upload
46
46
  from .model import User as User
47
47
 
48
- __version__ = "1.3.6"
48
+ __version__ = "1.3.8"
49
49
 
50
50
 
51
51
  MessageReceipt = MessageObject
@@ -31,10 +31,7 @@ from .protocol import ApiProtocol as ApiProtocol
31
31
  TConfig = TypeVar("TConfig", bound=Config)
32
32
  TE = TypeVar("TE", bound=Event, contravariant=True)
33
33
 
34
- MAPPING: dict[type[Config], type[BaseNetwork]] = {
35
- WebhookInfo: WebhookNetwork,
36
- WebsocketsInfo: WsNetwork,
37
- }
34
+ MAPPING: dict[type[Config], type[BaseNetwork]] = {WebhookInfo: WebhookNetwork, WebsocketsInfo: WsNetwork} # noqa
38
35
 
39
36
  _app: App | None = None
40
37
 
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass
1
+ from dataclasses import dataclass, field
2
2
 
3
3
  from yarl import URL
4
4
 
@@ -25,8 +25,8 @@ class WebsocketsInfo(Config):
25
25
  token: str | None = None
26
26
  secure: bool = False
27
27
  timeout: float | None = None
28
- identity: str = None # type: ignore
29
- api_base: URL = None # type: ignore
28
+ identity: str = field(init=False, default=None) # type: ignore
29
+ api_base: URL = field(init=False, default=None) # type: ignore
30
30
 
31
31
  def __post_init__(self):
32
32
  if self.path and not self.path.startswith("/"):
@@ -469,6 +469,15 @@ class Paragraph(Style):
469
469
  return "p"
470
470
 
471
471
 
472
+ class Blockquote(Style):
473
+ """<blockquote> 元素表示块引用"""
474
+
475
+ @property
476
+ @override
477
+ def tag(self) -> str:
478
+ return "blockquote"
479
+
480
+
472
481
  @dataclass(init=False, repr=False)
473
482
  class Message(Element):
474
483
  """<message> 元素的基本用法是表示一条消息。
@@ -34,7 +34,7 @@ class ModelBase:
34
34
  data = {}
35
35
  cls.before_parse(data)
36
36
  for name in cls.__dataclass_fields__:
37
- if name in raw:
37
+ if name in raw and raw[name] is not None:
38
38
  if name in cls.__converter__:
39
39
  data[name] = cls.__converter__[name](raw[name])
40
40
  else:
@@ -55,7 +55,7 @@ class ModelBase:
55
55
  keys = frozenset(k for k in keys if not k.startswith("_"))
56
56
 
57
57
  def parse1(cls_: type[Self], raw: dict, _keys=keys) -> Self:
58
- data = {k: v for k, v in raw.items() if k in _keys}
58
+ data = {k: v for k, v in raw.items() if k in _keys and v is not None}
59
59
  obj = cls_(**data) # type: ignore
60
60
  obj._raw_data = raw
61
61
  return obj
@@ -64,7 +64,7 @@ class ModelBase:
64
64
  data = {}
65
65
  cls_.before_parse(data)
66
66
  for name in _keys:
67
- if name in raw:
67
+ if name in raw and raw[name] is not None:
68
68
  if name in cls_.__converter__:
69
69
  data[name] = cls_.__converter__[name](raw[name])
70
70
  else:
@@ -7,13 +7,13 @@ import re
7
7
  import secrets
8
8
  import signal
9
9
  import threading
10
+ import traceback
10
11
  import urllib.parse
11
12
  from collections.abc import Awaitable, Callable, Iterable
12
13
  from contextlib import suppress
13
14
  from itertools import chain
14
15
  from pathlib import Path
15
16
  from tempfile import TemporaryDirectory
16
- from traceback import print_exc
17
17
  from typing import Any, TypeVar
18
18
 
19
19
  import aiohttp
@@ -103,10 +103,10 @@ async def _request_handler(action: str, request: StarletteRequest, func: RouteCa
103
103
  return Response(status_code=504, content="Request timeout")
104
104
  except ActionFailed as ae:
105
105
  logger.warning(ae)
106
- return Response(status_code=ae.CODE, content=str(ae))
106
+ return Response(status_code=ae.CODE, content=repr(ae))
107
107
  except Exception as e:
108
108
  logger.error(e)
109
- return Response(status_code=500, content=str(e))
109
+ return Response(status_code=500, content=repr(e))
110
110
  if isinstance(res, ModelBase):
111
111
  return JSONResponse(content=res.dump())
112
112
  if res and isinstance(res, list) and isinstance(res[0], ModelBase):
@@ -239,16 +239,22 @@ class Server(Service, RouterMixin):
239
239
  event.sn = self._sequence
240
240
  self._event_cache.append(event)
241
241
  self._sequence += 1
242
- for connection in self.connections:
243
- if not connection.alive:
244
- continue
245
- try:
246
- await connection.send({"op": Opcode.EVENT, "body": event.dump()})
247
- except (WebSocketDisconnect, RuntimeError):
248
- break
249
- except Exception as e:
250
- print_exc()
251
- logger.error(e)
242
+ connections = [conn for conn in self.connections if conn.alive]
243
+ payload = {"op": Opcode.EVENT, "body": event.dump()}
244
+ ws_tasks = [conn.send(payload) for conn in connections]
245
+ if ws_tasks:
246
+ results = await asyncio.gather(*ws_tasks, return_exceptions=True)
247
+ for conn, result in zip(connections, results):
248
+ if not result:
249
+ continue
250
+ if isinstance(result, (WebSocketDisconnect, RuntimeError)):
251
+ logger.warning(f"Connection {id(conn):x} closed: {result}")
252
+ else:
253
+ traceback.print_exception(type(result), result, result.__traceback__)
254
+ logger.error(result)
255
+ await conn.connection_closed()
256
+ if conn in self.connections:
257
+ self.connections.remove(conn)
252
258
  for hook in self.webhooks:
253
259
  try:
254
260
  async with self.session.post(
@@ -263,7 +269,7 @@ class Server(Service, RouterMixin):
263
269
  ) as resp:
264
270
  resp.raise_for_status()
265
271
  except Exception as e:
266
- print_exc()
272
+ traceback.print_exception(type(e), e, e.__traceback__)
267
273
  logger.error(e)
268
274
 
269
275
  async def websocket_server_handler(self, ws: WebSocket):
@@ -1,5 +1,5 @@
1
1
  from abc import abstractmethod
2
- from typing import TYPE_CHECKING, Union
2
+ from typing import TYPE_CHECKING, Generic, TypeVar, Union
3
3
 
4
4
  from launart import Service
5
5
  from starlette.responses import Response
@@ -16,10 +16,12 @@ if TYPE_CHECKING:
16
16
 
17
17
 
18
18
  LoginType = Union[Login, LoginPartial] # noqa: UP007
19
+ T = TypeVar("T")
19
20
 
20
21
 
21
- class Adapter(Service, RouterMixin):
22
+ class Adapter(Service, RouterMixin, Generic[T]):
22
23
  server: "Server"
24
+ config: T
23
25
 
24
26
  @abstractmethod
25
27
  def get_platform(self) -> str:
File without changes
File without changes