satori-python-client 0.17.7__tar.gz → 0.18.0__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,6 +1,6 @@
1
1
  includes = ["src/satori/client"]
2
2
  raw-dependencies = [
3
- "satori-python-core >= 0.17.0",
3
+ "satori-python-core >= 0.18.0",
4
4
  "graia-amnesia >= 0.11.0",
5
5
  ]
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: satori-python-client
3
- Version: 0.17.7
3
+ Version: 0.18.0
4
4
  Summary: Satori Protocol SDK for python, specify client 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>
@@ -19,7 +19,7 @@ Project-URL: Repository, https://github.com/RF-Tar-Railt/satori-python
19
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: satori-python-core>=0.17.0
22
+ Requires-Dist: satori-python-core>=0.18.0
23
23
  Requires-Dist: graia-amnesia>=0.11.0
24
24
  Description-Content-Type: text/markdown
25
25
 
@@ -30,17 +30,18 @@ Description-Content-Type: text/markdown
30
30
  [![PyPI](https://img.shields.io/pypi/v/satori-python)](https://pypi.org/project/satori-python)
31
31
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/satori-python)](https://www.python.org/)
32
32
 
33
- 基于 [Satori](https://satori.js.org/zh-CN/) 协议的 Python 开发工具包
33
+ 基于 [Satori](https://satori.chat/zh-CN/) 协议的 Python 开发工具包
34
34
 
35
35
  ## 协议介绍
36
36
 
37
- [Satori Protocol](https://satori.js.org/zh-CN/)
37
+ [Satori Protocol](https://satori.chat/zh-CN/)
38
38
 
39
39
  ### 协议端
40
40
 
41
41
  目前提供了 `satori` 协议实现的有:
42
42
 
43
43
  - [Chronocat](https://chronocat.vercel.app)
44
+ - [LLBot](https://www.llonebot.com/guide/introduction)
44
45
  - [nekobox](https://github.com/wyapx/nekobox)
45
46
  - Koishi (搭配 `@koishijs/plugin-server`)
46
47
 
@@ -78,6 +79,7 @@ pip install satori-python-server
78
79
  | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
79
80
  | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
80
81
  | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook |
82
+ | QQ | `pip install satori-python-adapter-qq` | satori.adapters.milky.main, satori.adapters.milky.websocket |
81
83
 
82
84
  ### 社区适配器
83
85
 
@@ -5,17 +5,18 @@
5
5
  [![PyPI](https://img.shields.io/pypi/v/satori-python)](https://pypi.org/project/satori-python)
6
6
  [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/satori-python)](https://www.python.org/)
7
7
 
8
- 基于 [Satori](https://satori.js.org/zh-CN/) 协议的 Python 开发工具包
8
+ 基于 [Satori](https://satori.chat/zh-CN/) 协议的 Python 开发工具包
9
9
 
10
10
  ## 协议介绍
11
11
 
12
- [Satori Protocol](https://satori.js.org/zh-CN/)
12
+ [Satori Protocol](https://satori.chat/zh-CN/)
13
13
 
14
14
  ### 协议端
15
15
 
16
16
  目前提供了 `satori` 协议实现的有:
17
17
 
18
18
  - [Chronocat](https://chronocat.vercel.app)
19
+ - [LLBot](https://www.llonebot.com/guide/introduction)
19
20
  - [nekobox](https://github.com/wyapx/nekobox)
20
21
  - Koishi (搭配 `@koishijs/plugin-server`)
21
22
 
@@ -53,6 +54,7 @@ pip install satori-python-server
53
54
  | OneBot V11 | `pip install satori-python-adapter-onebot11` | satori.adapters.onebot11.forward, satori.adapters.onebot11.reverse |
54
55
  | Console | `pip install satori-python-adapter-console` | satori.adapters.console |
55
56
  | Milky | `pip install satori-python-adapter-milky` | satori.adapters.milky.main, satori.adapters.milky.webhook |
57
+ | QQ | `pip install satori-python-adapter-qq` | satori.adapters.milky.main, satori.adapters.milky.websocket |
56
58
 
57
59
  ### 社区适配器
58
60
 
@@ -7,7 +7,7 @@ authors = [
7
7
  dependencies = [
8
8
  "aiohttp>=3.9.3",
9
9
  "launart>=0.8.2",
10
- "satori-python-core >= 0.17.0",
10
+ "satori-python-core >= 0.18.0",
11
11
  "graia-amnesia >= 0.11.0",
12
12
  ]
13
13
  description = "Satori Protocol SDK for python, specify client part"
@@ -24,7 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.12",
25
25
  "Operating System :: OS Independent",
26
26
  ]
27
- version = "0.17.7"
27
+ version = "0.18.0"
28
28
 
29
29
  [project.license]
30
30
  text = "MIT"
@@ -102,7 +102,9 @@ class App(Service):
102
102
  self.event_callbacks.append(callback)
103
103
 
104
104
  @overload
105
- def register_on(self, event_type: Literal[EventType.FRIEND_REQUEST]) -> Callable[
105
+ def register_on(
106
+ self, event_type: Literal[EventType.FRIEND_ADDED, EventType.FRIEND_REMOVED, EventType.FRIEND_REQUEST]
107
+ ) -> Callable[
106
108
  [Callable[[Account, events.UserEvent], Awaitable[Any]]],
107
109
  Callable[[Account, events.UserEvent], Awaitable[Any]],
108
110
  ]: ...
@@ -118,6 +120,15 @@ class App(Service):
118
120
  Callable[[Account, events.GuildEvent], Awaitable[Any]],
119
121
  ]: ...
120
122
 
123
+ @overload
124
+ def register_on(
125
+ self,
126
+ event_type: Literal[EventType.CHANNEL_ADDED, EventType.CHANNEL_REMOVED, EventType.CHANNEL_UPDATED],
127
+ ) -> Callable[
128
+ [Callable[[Account, events.ChannelEvent], Awaitable[Any]]],
129
+ Callable[[Account, events.ChannelEvent], Awaitable[Any]],
130
+ ]: ...
131
+
121
132
  @overload
122
133
  def register_on(
123
134
  self,
@@ -204,7 +215,12 @@ class App(Service):
204
215
 
205
216
  async def account_update(self, account: Account, state: LoginStatus):
206
217
  if self.lifecycle_callbacks:
207
- await asyncio.gather(*(callback(account, state) for callback in self.lifecycle_callbacks))
218
+ task = asyncio.gather(*(callback(account, state) for callback in self.lifecycle_callbacks))
219
+ try:
220
+ await task
221
+ except Exception:
222
+ traceback.print_exc()
223
+ task.cancel()
208
224
 
209
225
  async def post(self, event: Event, conn: BaseNetwork):
210
226
  if event.type == EventType.LOGIN_ADDED:
@@ -214,7 +230,7 @@ class App(Service):
214
230
  if not login.user:
215
231
  logger.warning(f"Received login-added event without user info: {login}")
216
232
  return
217
- login_sn = f"{login.user.id}@{id(conn):x}"
233
+ login_sn = f"{login.platform}_{login.user.id}@{id(conn):x}"
218
234
  account = Account(
219
235
  login,
220
236
  conn.config,
@@ -233,7 +249,7 @@ class App(Service):
233
249
  if not login.user:
234
250
  logger.warning(f"Received login-updated event without user info: {login}")
235
251
  return
236
- login_sn = f"{login.user.id}@{id(conn):x}"
252
+ login_sn = f"{login.platform}_{login.user.id}@{id(conn):x}"
237
253
  if login_sn not in self.accounts:
238
254
  if login.status == LoginStatus.ONLINE:
239
255
  account = Account(
@@ -267,13 +283,13 @@ class App(Service):
267
283
  if not login.user:
268
284
  logger.warning(f"Received login-removed event without user info: {login}")
269
285
  return
270
- login_sn = f"{login.user.id}@{id(conn):x}"
286
+ login_sn = f"{login.platform}_{login.user.id}@{id(conn):x}"
271
287
  if login_sn not in self.accounts:
272
288
  logger.warning(f"Received event for unknown account: {event}")
273
289
  return
274
290
  account = self.accounts[login_sn]
275
291
  else:
276
- login_sn = f"{event.login.user.id}@{id(conn):x}"
292
+ login_sn = f"{event.login.platform}_{event.login.user.id}@{id(conn):x}"
277
293
  if login_sn not in self.accounts:
278
294
  logger.warning(f"Received event for unknown account: {event}")
279
295
  return
@@ -72,7 +72,10 @@ class Account(Generic[TP]):
72
72
  for proxy_url in self.proxy_urls:
73
73
  if url.startswith(proxy_url):
74
74
  return self.config.api_base / "proxy" / url.lstrip("/")
75
- return URL(url)
75
+ ans = URL(url)
76
+ if not ans.scheme:
77
+ ans = URL(f"http://{url}")
78
+ return ans
76
79
 
77
80
  def __repr__(self):
78
81
  return f"<Account {self.self_id} ({self.platform})>"
@@ -15,7 +15,6 @@ from satori.model import (
15
15
  Login,
16
16
  Member,
17
17
  MessageObject,
18
- MessageReceipt,
19
18
  Meta,
20
19
  Order,
21
20
  PageDequeResult,
@@ -80,7 +79,7 @@ class Account(Generic[TP]):
80
79
  - 链接开头出现在 self_info.proxy_urls 中的某一项
81
80
  """
82
81
 
83
- async def send(self, event: Event, message: str | Iterable[str | Element]) -> list[MessageReceipt]:
82
+ async def send(self, event: Event, message: str | Iterable[str | Element]) -> list[MessageObject]:
84
83
  """发送消息。返回一个 `MessageObject` 对象构成的数组。
85
84
 
86
85
  Args:
@@ -95,26 +94,28 @@ class Account(Generic[TP]):
95
94
  """
96
95
 
97
96
  async def send_message(
98
- self, channel: str | Channel, message: str | Iterable[str | Element]
99
- ) -> list[MessageReceipt]:
97
+ self, channel: str | Channel, message: str | Iterable[str | Element], referrer: dict[str, Any] | None = None
98
+ ) -> list[MessageObject]:
100
99
  """发送消息。返回一个 `MessageObject` 对象构成的数组。
101
100
 
102
101
  Args:
103
102
  channel (str | Channel): 要发送的频道 ID
104
103
  message (str | Iterable[str | Element]): 要发送的消息
104
+ referrer (dict[str, Any] | None, optional): 消息来源信息,默认为 None
105
105
 
106
106
  Returns:
107
107
  list[MessageObject]: `MessageObject` 对象构成的数组
108
108
  """
109
109
 
110
110
  async def send_private_message(
111
- self, user: str | User, message: str | Iterable[str | Element]
112
- ) -> list[MessageReceipt]:
111
+ self, user: str | User, message: str | Iterable[str | Element], referrer: dict[str, Any] | None = None
112
+ ) -> list[MessageObject]:
113
113
  """发送私聊消息。返回一个 `MessageObject` 对象构成的数组。
114
114
 
115
115
  Args:
116
116
  user (str | User): 要发送的用户 ID
117
117
  message (str | Iterable[str | Element]): 要发送的消息
118
+ referrer (dict[str, Any] | None, optional): 消息来源信息,默认为 None
118
119
 
119
120
  Returns:
120
121
  list[MessageObject]: `MessageObject` 对象构成的数组
@@ -134,12 +135,15 @@ class Account(Generic[TP]):
134
135
  None: 该方法无返回值
135
136
  """
136
137
 
137
- async def message_create(self, channel_id: str, content: str) -> list[MessageReceipt]:
138
+ async def message_create(
139
+ self, channel_id: str, content: str, referrer: dict[str, Any] | None = None
140
+ ) -> list[MessageObject]:
138
141
  """发送消息。返回一个 `MessageObject` 对象构成的数组。
139
142
 
140
143
  Args:
141
144
  channel_id (str): 频道 ID
142
145
  content (str): 消息内容
146
+ referrer (dict[str, Any] | None, optional): 消息来源信息,默认为 None
143
147
 
144
148
  Returns:
145
149
  list[MessageObject]: `MessageObject` 对象构成的数组
@@ -583,7 +587,7 @@ class Account(Generic[TP]):
583
587
  """
584
588
  upload = upload_create
585
589
 
586
- async def download(self, url: str):
590
+ async def download(self, url: str) -> bytes:
587
591
  """访问内部链接。"""
588
592
 
589
593
  async def request_internal(self, url: str, method: str = "GET", **kwargs) -> dict:
@@ -0,0 +1,44 @@
1
+ from typing import Literal, overload
2
+
3
+ from aiohttp import ClientResponse
4
+
5
+ from satori.exception import (
6
+ BadRequestException,
7
+ ForbiddenException,
8
+ MethodNotAllowedException,
9
+ NotFoundException,
10
+ ServerException,
11
+ UnauthorizedException,
12
+ )
13
+ from satori.utils import decode
14
+
15
+
16
+ @overload
17
+ async def validate_response(resp: ClientResponse) -> dict: ...
18
+
19
+
20
+ @overload
21
+ async def validate_response(resp: ClientResponse, noreturn: Literal[True]) -> None: ...
22
+
23
+
24
+ async def validate_response(resp: ClientResponse, noreturn=False):
25
+ match resp.status:
26
+ case x if 200 <= x < 300:
27
+ if noreturn:
28
+ return
29
+ content = await resp.text()
30
+ return decode(content) if content else {}
31
+ case 400:
32
+ raise BadRequestException(await resp.text())
33
+ case 401:
34
+ raise UnauthorizedException(await resp.text())
35
+ case 403:
36
+ raise ForbiddenException(await resp.text())
37
+ case 404:
38
+ raise NotFoundException(await resp.text())
39
+ case 405:
40
+ raise MethodNotAllowedException(await resp.text())
41
+ case x if x >= 500:
42
+ raise ServerException(await resp.text())
43
+ case _:
44
+ resp.raise_for_status()
@@ -105,7 +105,7 @@ class WebhookNetwork(BaseNetwork[WebhookInfo]):
105
105
  for login in meta.logins:
106
106
  if not login.user:
107
107
  continue
108
- login_sn = f"{login.user.id}@{id(self):x}"
108
+ login_sn = f"{login.platform}_{login.user.id}@{id(self):x}"
109
109
  account = Account(login, self.config, meta.proxy_urls, self.app.default_api_cls)
110
110
  logger.info(f"account registered: {account}")
111
111
  (account.connected.set() if login.status == LoginStatus.ONLINE else account.connected.clear())
@@ -117,7 +117,7 @@ class WebhookNetwork(BaseNetwork[WebhookInfo]):
117
117
  logger.info(f"{self.id} Webhook server exiting...")
118
118
  self.close_signal.set()
119
119
  for v in list(self.app.accounts.values()):
120
- if (identity := f"{v.self_id}@{id(self):x}") in self.accounts:
120
+ if (identity := f"{v.platform}_{v.self_id}@{id(self):x}") in self.accounts:
121
121
  v.connected.clear()
122
122
  await self.app.account_update(v, LoginStatus.OFFLINE)
123
123
  del self.app.accounts[identity]
@@ -107,7 +107,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
107
107
  for login in ready.logins:
108
108
  if not login.user:
109
109
  continue
110
- login_sn = f"{login.user.id}@{id(self):x}"
110
+ login_sn = f"{login.platform}_{login.user.id}@{id(self):x}"
111
111
  if login_sn in self.app.accounts:
112
112
  account = self.app.accounts[login_sn]
113
113
  self.accounts[login_sn] = account
@@ -162,7 +162,7 @@ class WsNetwork(BaseNetwork[WebsocketsInfo]):
162
162
  self.close_signal.set()
163
163
  self.connection = None
164
164
  for v in list(self.app.accounts.values()):
165
- if (identity := f"{v.self_id}@{id(self):x}") in self.accounts:
165
+ if (identity := f"{v.platform}_{v.self_id}@{id(self):x}") in self.accounts:
166
166
  v.connected.clear()
167
167
  await self.app.account_update(v, LoginStatus.OFFLINE)
168
168
  del self.app.accounts[identity]
@@ -20,7 +20,6 @@ from satori.model import (
20
20
  LoginPartial,
21
21
  Member,
22
22
  MessageObject,
23
- MessageReceipt,
24
23
  Meta,
25
24
  Order,
26
25
  PageDequeResult,
@@ -45,7 +44,7 @@ class ApiProtocol:
45
44
  self.session = ClientSession()
46
45
  self.timeout = ClientTimeout(self.account.config.timeout or 300)
47
46
 
48
- async def download(self, url: str):
47
+ async def download(self, url: str) -> bytes:
49
48
  """访问资源链接。"""
50
49
  endpoint = self.account.ensure_url(url)
51
50
  aio = Launart.current().get_component(AiohttpClientService)
@@ -99,54 +98,58 @@ class ApiProtocol:
99
98
  ) as resp:
100
99
  return await validate_response(resp)
101
100
 
102
- async def send(self, event: Event, message: str | Iterable[str | Element]) -> list[MessageReceipt]:
103
- """发送消息。返回一个 `MessageReceipt` 对象构成的数组。
101
+ async def send(self, event: Event, message: str | Iterable[str | Element]) -> list[MessageObject]:
102
+ """发送消息。返回一个 `MessageObject` 对象构成的数组。
104
103
 
105
104
  Args:
106
105
  event (Event): 当前事件(上下文)
107
106
  message (str | Iterable[str | Element]): 要发送的消息
108
107
 
109
108
  Returns:
110
- list[MessageReceipt]: `MessageReceipt` 对象构成的数组
109
+ list[MessageObject]: `MessageObject` 对象构成的数组
111
110
 
112
111
  Raises:
113
112
  RuntimeError: 传入的事件缺少 `channel` 对象
114
113
  """
115
114
  if not event.channel:
116
115
  raise RuntimeError("Event cannot be replied to!")
117
- return await self.send_message(event.channel.id, message)
116
+ return await self.send_message(event.channel.id, message, event.referrer)
118
117
 
119
118
  async def send_message(
120
- self, channel: str | Channel, message: str | Iterable[str | Element]
121
- ) -> list[MessageReceipt]:
122
- """发送消息。返回一个 `MessageReceipt` 对象构成的数组。
119
+ self, channel: str | Channel, message: str | Iterable[str | Element], referrer: dict[str, Any] | None = None
120
+ ) -> list[MessageObject]:
121
+ """发送消息。返回一个 `MessageObject` 对象构成的数组。
123
122
 
124
123
  Args:
125
124
  channel (str | Channel): 要发送的频道 ID
126
125
  message (str | Iterable[str | Element]): 要发送的消息
126
+ referrer (dict[str, Any] | None): 消息来源信息,默认为 None
127
127
 
128
128
  Returns:
129
- list[MessageReceipt]: `MessageReceipt` 对象构成的数组
129
+ list[MessageObject]: `MessageObject` 对象构成的数组
130
130
  """
131
131
  channel_id = channel.id if isinstance(channel, Channel) else channel
132
132
  msg = message if isinstance(message, str) else "".join(str(i) for i in message)
133
- return await self.message_create(channel_id=channel_id, content=msg)
133
+ return await self.message_create(channel_id=channel_id, content=msg, referrer=referrer)
134
134
 
135
135
  async def send_private_message(
136
- self, user: str | User, message: str | Iterable[str | Element]
137
- ) -> list[MessageReceipt]:
138
- """发送私聊消息。返回一个 `MessageReceipt` 对象构成的数组。
136
+ self, user: str | User, message: str | Iterable[str | Element], referrer: dict[str, Any] | None = None
137
+ ) -> list[MessageObject]:
138
+ """发送私聊消息。返回一个 `MessageObject` 对象构成的数组。
139
139
 
140
140
  Args:
141
141
  user (str | User): 要发送的用户 ID
142
142
  message (str | Iterable[str | Element]): 要发送的消息
143
+ referrer (dict[str, Any] | None): 消息来源信息,默认为 None
143
144
 
144
145
  Returns:
145
- list[MessageReceipt]: `MessageReceipt` 对象构成的数组
146
+ list[MessageObject]: `MessageObject` 对象构成的数组
146
147
  """
147
148
  user_id = user.id if isinstance(user, User) else user
148
149
  channel = await self.user_channel_create(user_id=user_id)
149
- return await self.message_create(channel_id=channel.id, content="".join(str(i) for i in message))
150
+ return await self.message_create(
151
+ channel_id=channel.id, content="".join(str(i) for i in message), referrer=referrer
152
+ )
150
153
 
151
154
  async def update_message(
152
155
  self, channel: str | Channel, message_id: str, message: str | Iterable[str | Element]
@@ -169,22 +172,25 @@ class ApiProtocol:
169
172
  content=msg,
170
173
  )
171
174
 
172
- async def message_create(self, channel_id: str, content: str) -> list[MessageReceipt]:
173
- """发送消息。返回一个 `MessageReceipt` 对象构成的数组。
175
+ async def message_create(
176
+ self, channel_id: str, content: str, referrer: dict[str, Any] | None = None
177
+ ) -> list[MessageObject]:
178
+ """发送消息。返回一个 `MessageObject` 对象构成的数组。
174
179
 
175
180
  Args:
176
181
  channel_id (str): 频道 ID
177
182
  content (str): 消息内容
183
+ referrer (dict[str, Any] | None): 消息来源信息,默认为 None
178
184
 
179
185
  Returns:
180
- list[MessageReceipt]: `MessageReceipt` 对象构成的数组
186
+ list[MessageObject]: `MessageObject` 对象构成的数组
181
187
  """
182
188
  res = await self.call_api(
183
189
  Api.MESSAGE_CREATE,
184
- {"channel_id": channel_id, "content": content},
190
+ {"channel_id": channel_id, "content": content, "referrer": referrer},
185
191
  )
186
192
  res = cast("list[dict]", res)
187
- return [MessageReceipt.parse(i) for i in res]
193
+ return [MessageObject.parse(i) for i in res]
188
194
 
189
195
  async def message_get(self, channel_id: str, message_id: str) -> MessageObject:
190
196
  """获取特定消息。返回一个 `MessageObject` 对象。
@@ -793,7 +799,8 @@ class ApiProtocol:
793
799
  Returns:
794
800
  list[Login]: `Login` 对象构成的数组
795
801
  """
796
- return (await self.meta_get()).logins
802
+ res = await self.call_api("admin/login.list")
803
+ return [LoginPartial.parse(i) for i in res]
797
804
 
798
805
  async def webhook_create(self, url: str, token: str | None = None):
799
806
  """创建 Webhook。"""
@@ -1,42 +0,0 @@
1
- from typing import Literal, overload
2
-
3
- from aiohttp import ClientResponse
4
-
5
- from satori.exception import (
6
- BadRequestException,
7
- ForbiddenException,
8
- MethodNotAllowedException,
9
- NotFoundException,
10
- ServerException,
11
- UnauthorizedException,
12
- )
13
- from satori.utils import decode
14
-
15
-
16
- @overload
17
- async def validate_response(resp: ClientResponse) -> dict: ...
18
-
19
-
20
- @overload
21
- async def validate_response(resp: ClientResponse, noreturn: Literal[True]) -> None: ...
22
-
23
-
24
- async def validate_response(resp: ClientResponse, noreturn=False):
25
- if 200 <= resp.status < 300:
26
- if noreturn:
27
- return
28
- return decode(content) if (content := await resp.text()) else {}
29
- elif resp.status == 400:
30
- raise BadRequestException(await resp.text())
31
- elif resp.status == 401:
32
- raise UnauthorizedException(await resp.text())
33
- elif resp.status == 403:
34
- raise ForbiddenException(await resp.text())
35
- elif resp.status == 404:
36
- raise NotFoundException(await resp.text())
37
- elif resp.status == 405:
38
- raise MethodNotAllowedException(await resp.text())
39
- elif resp.status >= 500:
40
- raise ServerException(await resp.text())
41
- else:
42
- resp.raise_for_status()