satori-python 0.11.1__tar.gz → 0.11.3__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 (25) hide show
  1. {satori_python-0.11.1 → satori_python-0.11.3}/PKG-INFO +1 -1
  2. {satori_python-0.11.1 → satori_python-0.11.3}/pyproject.toml +1 -1
  3. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/__init__.py +1 -1
  4. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/__init__.py +23 -2
  5. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/element.py +81 -32
  6. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/server/__init__.py +24 -3
  7. {satori_python-0.11.1 → satori_python-0.11.3}/LICENSE +0 -0
  8. {satori_python-0.11.1 → satori_python-0.11.3}/README.md +0 -0
  9. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/account.py +0 -0
  10. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/account.pyi +0 -0
  11. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/network/__init__.py +0 -0
  12. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/network/base.py +0 -0
  13. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/network/webhook.py +0 -0
  14. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/network/websocket.py +0 -0
  15. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/client/session.py +0 -0
  16. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/config.py +0 -0
  17. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/const.py +0 -0
  18. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/event.py +0 -0
  19. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/exception.py +0 -0
  20. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/model.py +0 -0
  21. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/parser.py +0 -0
  22. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/server/adapter.py +0 -0
  23. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/server/conection.py +0 -0
  24. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/server/model.py +0 -0
  25. {satori_python-0.11.1 → satori_python-0.11.3}/src/satori/server/route.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python
3
- Version: 0.11.1
3
+ Version: 0.11.3
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>
@@ -28,7 +28,7 @@ classifiers = [
28
28
  "Programming Language :: Python :: 3.12",
29
29
  "Operating System :: OS Independent",
30
30
  ]
31
- version = "0.11.1"
31
+ version = "0.11.3"
32
32
 
33
33
  [project.license]
34
34
  text = "MIT"
@@ -39,4 +39,4 @@ from .model import MessageObject as MessageObject
39
39
  from .model import Role as Role
40
40
  from .model import User as User
41
41
 
42
- __version__ = "0.11.1"
42
+ __version__ = "0.11.3"
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import functools
4
5
  import signal
6
+ import threading
5
7
  from functools import wraps
6
8
  from typing import Any, Awaitable, Callable, Iterable, Literal, TypeVar, overload
7
9
 
@@ -211,8 +213,27 @@ class App(Service):
211
213
  manager.add_component(self)
212
214
  manager.launch_blocking(loop=loop, stop_signal=stop_signal)
213
215
 
214
- async def run_async(self, manager: Launart | None = None):
216
+ async def run_async(
217
+ self,
218
+ manager: Launart | None = None,
219
+ stop_signal: Iterable[signal.Signals] = (signal.SIGINT,),
220
+ ):
215
221
  if manager is None:
216
222
  manager = it(Launart)
217
223
  manager.add_component(self)
218
- await manager.launch()
224
+ handled_signals: dict[signal.Signals, Any] = {}
225
+ launch_task = asyncio.create_task(manager.launch(), name="amnesia-launch")
226
+ signal_handler = functools.partial(manager._on_sys_signal, main_task=launch_task)
227
+ if threading.current_thread() is threading.main_thread(): # pragma: worst case
228
+ try:
229
+ for sig in stop_signal:
230
+ handled_signals[sig] = signal.getsignal(sig)
231
+ signal.signal(sig, signal_handler)
232
+ except ValueError: # pragma: no cover
233
+ # `signal.signal` may throw if `threading.main_thread` does
234
+ # not support signals
235
+ handled_signals.clear()
236
+ await launch_task
237
+ for sig, handler in handled_signals.items():
238
+ if signal.getsignal(sig) is signal_handler:
239
+ signal.signal(sig, handler)
@@ -2,7 +2,7 @@ from base64 import b64encode
2
2
  from dataclasses import InitVar, dataclass, field, fields
3
3
  from io import BytesIO
4
4
  from pathlib import Path
5
- from typing import Any, Dict, List, Optional, TypeVar, Union, get_args
5
+ from typing import Any, ClassVar, Dict, List, Optional, Tuple, TypeVar, Union, get_args
6
6
  from typing_extensions import override
7
7
 
8
8
  from .parser import Element as RawElement
@@ -11,15 +11,23 @@ from .parser import escape, param_case
11
11
  TE = TypeVar("TE", bound="Element")
12
12
 
13
13
 
14
- @dataclass
14
+ @dataclass(repr=False)
15
15
  class Element:
16
- _attrs: Dict[str, Any] = field(init=False, default_factory=dict, repr=False)
16
+ _attrs: Dict[str, Any] = field(init=False, default_factory=dict)
17
17
  _children: List["Element"] = field(init=False, default_factory=list)
18
18
 
19
+ __names__: ClassVar[Tuple[str, ...]]
20
+
19
21
  @property
20
22
  def tag(self) -> str:
21
23
  return self.__class__.__name__.lower()
22
24
 
25
+ @classmethod
26
+ def unpack(cls, attrs: Dict[str, Any]):
27
+ obj = cls(**{k: v for k, v in attrs.items() if k in cls.__names__})
28
+ obj._attrs.update({k: v for k, v in attrs.items() if k not in cls.__names__})
29
+ return obj
30
+
23
31
  def __post_init__(self):
24
32
  for f in fields(self):
25
33
  if f.name in ("_attrs", "_children"):
@@ -62,6 +70,13 @@ class Element:
62
70
  def __str__(self) -> str:
63
71
  return self.dumps()
64
72
 
73
+ def __repr__(self) -> str:
74
+ args = {**self._attrs}
75
+ elem = f"{self.__class__.__name__}(" + ", ".join(f"{k}={v!r}" for k, v in args.items())
76
+ if self._children:
77
+ elem += ", { " + ", ".join(repr(i) for i in self._children) + " }"
78
+ return elem + ")"
79
+
65
80
  def __call__(self, *content: Union[str, "Element"]):
66
81
  self._children.extend(Text(i) if isinstance(i, str) else i for i in content)
67
82
  self.__post_call__()
@@ -69,8 +84,11 @@ class Element:
69
84
 
70
85
  def __post_call__(self): ...
71
86
 
87
+ def __getitem__(self, key: str) -> Any:
88
+ return self._attrs[key]
72
89
 
73
- @dataclass
90
+
91
+ @dataclass(repr=False)
74
92
  class Text(Element):
75
93
  """一段纯文本。"""
76
94
 
@@ -80,8 +98,10 @@ class Text(Element):
80
98
  def dumps(self, strip: bool = False) -> str:
81
99
  return self.text if strip else escape(self.text)
82
100
 
101
+ __names__ = ("text",)
83
102
 
84
- @dataclass
103
+
104
+ @dataclass(repr=False)
85
105
  class At(Element):
86
106
  """<at> 元素用于提及某个或某些用户。"""
87
107
 
@@ -101,8 +121,10 @@ class At(Element):
101
121
  def all(here: bool = False) -> "At":
102
122
  return At(type="here" if here else "all")
103
123
 
124
+ __names__ = ("id", "name", "role", "type")
104
125
 
105
- @dataclass
126
+
127
+ @dataclass(repr=False)
106
128
  class Sharp(Element):
107
129
  """<sharp> 元素用于提及某个频道。"""
108
130
 
@@ -114,7 +136,9 @@ class Sharp(Element):
114
136
  class Link(Element):
115
137
  """<a> 元素用于显示一个链接。"""
116
138
 
117
- url: str
139
+ href: str
140
+
141
+ __names__ = ("href",)
118
142
 
119
143
  def __post_call__(self):
120
144
  if not self._children:
@@ -128,18 +152,20 @@ class Link(Element):
128
152
  def tag(self) -> str:
129
153
  return "a"
130
154
 
131
- @override
132
- def attributes(self) -> str:
133
- return f' href="{escape(self.url)}"'
155
+ @property
156
+ def url(self) -> str:
157
+ return self.href
134
158
 
135
159
 
136
- @dataclass
160
+ @dataclass(repr=False)
137
161
  class Resource(Element):
138
162
  src: str
139
163
  extra: InitVar[Optional[Dict[str, Any]]] = None
140
164
  cache: Optional[bool] = None
141
165
  timeout: Optional[str] = None
142
166
 
167
+ __names__ = ("src",)
168
+
143
169
  @classmethod
144
170
  def of(
145
171
  cls,
@@ -173,44 +199,58 @@ class Resource(Element):
173
199
  self._attrs.update(extra)
174
200
 
175
201
 
176
- @dataclass
202
+ @dataclass(repr=False)
177
203
  class Image(Resource):
178
204
  """<img> 元素用于表示图片。"""
179
205
 
180
206
  width: Optional[int] = None
181
207
  height: Optional[int] = None
182
208
 
209
+ __names__ = ("src", "width", "height")
210
+
183
211
  @property
184
212
  @override
185
213
  def tag(self) -> str:
186
214
  return "img"
187
215
 
188
216
 
189
- @dataclass
217
+ @dataclass(repr=False)
190
218
  class Audio(Resource):
191
219
  """<audio> 元素用于表示语音。"""
192
220
 
193
- pass
221
+ duration: Optional[int] = None
222
+ poster: Optional[str] = None
194
223
 
224
+ __names__ = ("src", "duration", "poster")
195
225
 
196
- @dataclass
226
+
227
+ @dataclass(repr=False)
197
228
  class Video(Resource):
198
229
  """<video> 元素用于表示视频。"""
199
230
 
200
- pass
231
+ width: Optional[int] = None
232
+ height: Optional[int] = None
233
+ duration: Optional[int] = None
234
+ poster: Optional[str] = None
201
235
 
236
+ __names__ = ("src", "width", "height", "duration", "poster")
202
237
 
203
- @dataclass
238
+
239
+ @dataclass(repr=False)
204
240
  class File(Resource):
205
241
  """<file> 元素用于表示文件。"""
206
242
 
207
- pass
243
+ poster: Optional[str] = None
208
244
 
245
+ __names__ = ("src", "poster")
209
246
 
210
- @dataclass(init=False)
247
+
248
+ @dataclass(init=False, repr=False)
211
249
  class Style(Element):
212
250
  """样式元素的基类。"""
213
251
 
252
+ __names__ = ()
253
+
214
254
  def __init__(self, *text: Union[str, Text, "Style"]):
215
255
  super().__init__()
216
256
  self.__call__(*text)
@@ -311,13 +351,15 @@ class Paragraph(Style):
311
351
  return "p"
312
352
 
313
353
 
314
- @dataclass(init=False)
354
+ @dataclass(init=False, repr=False)
315
355
  class Message(Element):
316
356
  """<message> 元素的基本用法是表示一条消息。
317
357
 
318
358
  子元素对应于消息的内容。如果其没有子元素,则消息不会被发送。
319
359
  """
320
360
 
361
+ __names__ = ("id", "forward")
362
+
321
363
  id: Optional[str]
322
364
  forward: Optional[bool]
323
365
 
@@ -333,7 +375,6 @@ class Message(Element):
333
375
  self.__call__(*content or [])
334
376
 
335
377
 
336
- @dataclass
337
378
  class Quote(Message):
338
379
  """<quote> 元素用于表示对消息引用。
339
380
 
@@ -343,16 +384,18 @@ class Quote(Message):
343
384
  pass
344
385
 
345
386
 
346
- @dataclass
387
+ @dataclass(repr=False)
347
388
  class Author(Element):
348
389
  """<author> 元素用于表示消息的作者。它的子元素会被渲染为作者的名字。"""
349
390
 
350
391
  id: str
351
- nickname: Optional[str] = None
392
+ name: Optional[str] = None
352
393
  avatar: Optional[str] = None
353
394
 
395
+ __names__ = ("id", "name", "avatar")
354
396
 
355
- @dataclass
397
+
398
+ @dataclass(repr=False)
356
399
  class Button(Element):
357
400
  """<button> 元素用于表示一个按钮。它的子元素会被渲染为按钮的文本。"""
358
401
 
@@ -362,6 +405,8 @@ class Button(Element):
362
405
  text: Optional[str] = None
363
406
  theme: Optional[str] = None
364
407
 
408
+ __names__ = ("type", "id", "href", "text", "theme")
409
+
365
410
  @classmethod
366
411
  def action(cls, button_id: str, theme: Optional[str] = None):
367
412
  return Button("action", id=button_id, theme=theme)
@@ -395,12 +440,14 @@ class Button(Element):
395
440
  raise ValueError("Button can only have one Text child")
396
441
 
397
442
 
398
- @dataclass(init=False)
443
+ @dataclass(init=False, repr=False)
399
444
  class Custom(Element):
400
445
  """自定义元素用于构造标准元素以外的元素"""
401
446
 
402
447
  type: str
403
448
 
449
+ __names__ = ()
450
+
404
451
  def __init__(
405
452
  self,
406
453
  type: str,
@@ -424,12 +471,14 @@ class Custom(Element):
424
471
  return self.type
425
472
 
426
473
 
427
- @dataclass
474
+ @dataclass(repr=False)
428
475
  class Raw(Element):
429
476
  """Raw 元素表示原始文本"""
430
477
 
431
478
  content: str
432
479
 
480
+ __names__ = ()
481
+
433
482
  @override
434
483
  def dumps(self, strip: bool = False):
435
484
  return self.content if strip else escape(self.content)
@@ -480,25 +529,25 @@ def transform(elements: List[RawElement]) -> List[Element]:
480
529
  tag = elem.tag()
481
530
  if tag in ELEMENT_TYPE_MAP:
482
531
  seg_cls = ELEMENT_TYPE_MAP[tag]
483
- msg.append(seg_cls(**elem.attrs))
532
+ msg.append(seg_cls.unpack(elem.attrs))
484
533
  elif tag in ("a", "link"):
485
- link = Link(elem.attrs["href"])
534
+ link = Link.unpack(elem.attrs)
486
535
  if elem.children:
487
536
  link(*transform(elem.children))
488
537
  msg.append(link)
489
538
  elif tag == "button":
490
- button = Button(**elem.attrs)
539
+ button = Button.unpack(elem.attrs)
491
540
  if elem.children:
492
541
  button(*transform(elem.children))
493
542
  elif tag in STYLE_TYPE_MAP:
494
543
  seg_cls = STYLE_TYPE_MAP[tag]
495
- msg.append(seg_cls()(*transform(elem.children)))
544
+ msg.append(seg_cls.unpack(elem.attrs)(*transform(elem.children)))
496
545
  elif tag in ("br", "newline"):
497
546
  msg.append(Br())
498
547
  elif tag == "message":
499
- msg.append(Message(**elem.attrs)(*transform(elem.children)))
548
+ msg.append(Message.unpack(elem.attrs)(*transform(elem.children)))
500
549
  elif tag == "quote":
501
- msg.append(Quote(**elem.attrs)(*transform(elem.children)))
550
+ msg.append(Quote.unpack(elem.attrs)(*transform(elem.children)))
502
551
  else:
503
552
  msg.append(Custom(elem.type, elem.attrs)(*transform(elem.children)))
504
553
  return msg
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import functools
4
5
  import signal
6
+ import threading
5
7
  from contextlib import suppress
6
8
  from traceback import print_exc
7
- from typing import Callable, Iterable, Literal, cast, overload
9
+ from typing import Any, Callable, Iterable, Literal, cast, overload
8
10
 
9
11
  import aiohttp
10
12
  from creart import it
@@ -383,8 +385,27 @@ class Server(Service):
383
385
  manager.add_component(self)
384
386
  manager.launch_blocking(loop=loop, stop_signal=stop_signal)
385
387
 
386
- async def run_async(self, manager: Launart | None = None):
388
+ async def run_async(
389
+ self,
390
+ manager: Launart | None = None,
391
+ stop_signal: Iterable[signal.Signals] = (signal.SIGINT,),
392
+ ):
387
393
  if manager is None:
388
394
  manager = it(Launart)
389
395
  manager.add_component(self)
390
- await manager.launch()
396
+ handled_signals: dict[signal.Signals, Any] = {}
397
+ launch_task = asyncio.create_task(manager.launch(), name="amnesia-launch")
398
+ signal_handler = functools.partial(manager._on_sys_signal, main_task=launch_task)
399
+ if threading.current_thread() is threading.main_thread(): # pragma: worst case
400
+ try:
401
+ for sig in stop_signal:
402
+ handled_signals[sig] = signal.getsignal(sig)
403
+ signal.signal(sig, signal_handler)
404
+ except ValueError: # pragma: no cover
405
+ # `signal.signal` may throw if `threading.main_thread` does
406
+ # not support signals
407
+ handled_signals.clear()
408
+ await launch_task
409
+ for sig, handler in handled_signals.items():
410
+ if signal.getsignal(sig) is signal_handler:
411
+ signal.signal(sig, handler)
File without changes
File without changes