Skip to content

Commit a3fdd23

Browse files
♻️ Tighten prepared transport request typing
1 parent 4cddca8 commit a3fdd23

2 files changed

Lines changed: 70 additions & 59 deletions

File tree

nonebot/adapters/discord/serialization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def _build_multipart_payload(
179179

180180
def encode_prepared_request(
181181
prepared: PreparedRequest,
182-
) -> dict[Literal["files", "json"], Any]:
182+
) -> EncodedPreparedRequest:
183183
payload = encode_model_json_data(
184184
prepared.body,
185185
include=prepared.include,

tests/test_api_nullability.py

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime, timezone
22
import json
3+
from typing import Any
34

45
from nonebot.adapters.discord.api import (
56
ActionRow,
@@ -16,25 +17,41 @@
1617
)
1718
from nonebot.adapters.discord.api.types import UNSET
1819
from nonebot.adapters.discord.api.utils import parse_data, parse_forum_thread_message
19-
from nonebot.adapters.discord.serialization import encode_prepared_request
20+
from nonebot.adapters.discord.serialization import (
21+
PreparedRequest,
22+
encode_prepared_request,
23+
)
2024
from nonebot.adapters.discord.utils import omit_unset
2125

2226

27+
def _json_payload(prepared: PreparedRequest) -> dict[str, Any]:
28+
encoded = encode_prepared_request(prepared)
29+
assert "json" in encoded
30+
return encoded["json"]
31+
32+
33+
def _payload_json_text(prepared: PreparedRequest) -> str:
34+
encoded = encode_prepared_request(prepared)
35+
assert "files" in encoded
36+
payload_json = encoded["files"]["payload_json"]
37+
assert isinstance(payload_json, tuple)
38+
assert len(payload_json) >= 2
39+
payload_body = payload_json[1]
40+
assert isinstance(payload_body, (bytes, str))
41+
return payload_body.decode() if isinstance(payload_body, bytes) else payload_body
42+
43+
2344
def _assert_transport_datetime(actual: str, expected: datetime) -> None:
2445
assert datetime.fromisoformat(actual.replace("Z", "+00:00")) == expected
2546

2647

2748
def test_parse_data_keeps_explicit_null() -> None:
28-
payload = encode_prepared_request(parse_data({"content": None}, MessageEditParams))[
29-
"json"
30-
]
49+
payload = _json_payload(parse_data({"content": None}, MessageEditParams))
3150
assert payload == {"content": None}
3251

3352

3453
def test_parse_data_omits_unset() -> None:
35-
payload = encode_prepared_request(
36-
parse_data({"content": UNSET}, MessageEditParams)
37-
)["json"]
54+
payload = _json_payload(parse_data({"content": UNSET}, MessageEditParams))
3855
assert payload == {}
3956

4057

@@ -44,61 +61,59 @@ def test_omit_unset_filters_unset_in_list() -> None:
4461

4562

4663
def test_parse_data_message_send_omits_unset_fields() -> None:
47-
payload = encode_prepared_request(parse_data({}, MessageSend))["json"]
64+
payload = _json_payload(parse_data({}, MessageSend))
4865
assert payload == {}
4966

5067

5168
def test_parse_data_execute_webhook_omits_unset_fields() -> None:
52-
payload = encode_prepared_request(parse_data({}, ExecuteWebhookParams))["json"]
69+
payload = _json_payload(parse_data({}, ExecuteWebhookParams))
5370
assert payload == {}
5471

5572

5673
def test_parse_data_serializes_embed_timestamp() -> None:
5774
timestamp = datetime(2026, 3, 14, 12, 0, tzinfo=timezone.utc)
58-
payload = encode_prepared_request(
75+
payload = _json_payload(
5976
parse_data(
6077
{"embeds": [Embed(timestamp=timestamp)]},
6178
MessageSend,
6279
)
63-
)["json"]
80+
)
6481

6582
_assert_transport_datetime(payload["embeds"][0]["timestamp"], timestamp)
6683

6784

6885
def test_parse_data_multipart_keeps_null_attachments() -> None:
69-
res = encode_prepared_request(
70-
parse_data(
71-
{
72-
"files": [File(content=b"1", filename="a.txt")],
73-
"attachments": None,
74-
},
75-
MessageEditParams,
86+
payload = json.loads(
87+
_payload_json_text(
88+
parse_data(
89+
{
90+
"files": [File(content=b"1", filename="a.txt")],
91+
"attachments": None,
92+
},
93+
MessageEditParams,
94+
)
7695
)
7796
)
78-
multipart = res["files"]
79-
_, payload_json, _ = multipart["payload_json"]
80-
payload = json.loads(payload_json)
8197
assert payload["attachments"] is None
8298

8399

84100
def test_parse_data_multipart_maps_attachment_id() -> None:
85-
res = encode_prepared_request(
86-
parse_data(
87-
{
88-
"files": [File(content=b"1", filename="a.txt")],
89-
"attachments": [{"filename": "a.txt"}],
90-
},
91-
MessageEditParams,
101+
payload = json.loads(
102+
_payload_json_text(
103+
parse_data(
104+
{
105+
"files": [File(content=b"1", filename="a.txt")],
106+
"attachments": [{"filename": "a.txt"}],
107+
},
108+
MessageEditParams,
109+
)
92110
)
93111
)
94-
multipart = res["files"]
95-
_, payload_json, _ = multipart["payload_json"]
96-
payload = json.loads(payload_json)
97112
assert payload["attachments"][0]["id"] == 0
98113

99114

100115
def test_parse_data_keeps_action_row_type_for_components() -> None:
101-
payload = encode_prepared_request(
116+
payload = _json_payload(
102117
parse_data(
103118
{
104119
"components": [
@@ -115,54 +130,50 @@ def test_parse_data_keeps_action_row_type_for_components() -> None:
115130
},
116131
MessageSend,
117132
)
118-
)["json"]
133+
)
119134
assert "content" not in payload
120135
assert int(payload["components"][0]["type"]) == int(ComponentType.ActionRow)
121136

122137

123138
def test_parse_forum_thread_message_keeps_name() -> None:
124-
payload = encode_prepared_request(
139+
payload = _json_payload(
125140
parse_forum_thread_message({"name": "thread-name", "content": "hello"})
126-
)["json"]
141+
)
127142
assert payload["name"] == "thread-name"
128143

129144

130145
def test_parse_forum_thread_message_without_content() -> None:
131-
payload = encode_prepared_request(
132-
parse_forum_thread_message({"name": "thread-name"})
133-
)["json"]
146+
payload = _json_payload(parse_forum_thread_message({"name": "thread-name"}))
134147
assert payload["message"] == {}
135148

136149

137150
def test_parse_forum_thread_message_serializes_embed_timestamp_in_multipart() -> None:
138151
timestamp = datetime(2026, 3, 14, 12, 0, tzinfo=timezone.utc)
139-
res = encode_prepared_request(
140-
parse_forum_thread_message(
141-
{
142-
"name": "thread-name",
143-
"files": [File(content=b"1", filename="a.txt")],
144-
"embeds": [Embed(timestamp=timestamp)],
145-
}
152+
payload = json.loads(
153+
_payload_json_text(
154+
parse_forum_thread_message(
155+
{
156+
"name": "thread-name",
157+
"files": [File(content=b"1", filename="a.txt")],
158+
"embeds": [Embed(timestamp=timestamp)],
159+
}
160+
)
146161
)
147162
)
148-
multipart = res["files"]
149-
_, payload_json, _ = multipart["payload_json"]
150-
payload = json.loads(payload_json)
151163

152164
_assert_transport_datetime(payload["message"]["embeds"][0]["timestamp"], timestamp)
153165

154166

155167
def test_parse_forum_thread_message_maps_message_attachment_id() -> None:
156-
res = encode_prepared_request(
157-
parse_forum_thread_message(
158-
{
159-
"name": "thread-name",
160-
"files": [File(content=b"1", filename="a.txt")],
161-
"attachments": [{"filename": "a.txt"}],
162-
}
168+
payload = json.loads(
169+
_payload_json_text(
170+
parse_forum_thread_message(
171+
{
172+
"name": "thread-name",
173+
"files": [File(content=b"1", filename="a.txt")],
174+
"attachments": [{"filename": "a.txt"}],
175+
}
176+
)
163177
)
164178
)
165-
multipart = res["files"]
166-
_, payload_json, _ = multipart["payload_json"]
167-
payload = json.loads(payload_json)
168179
assert payload["message"]["attachments"][0]["id"] == 0

0 commit comments

Comments
 (0)