From 3a27bfd2f96e0eafaa54188e41bec811be333ca1 Mon Sep 17 00:00:00 2001 From: beagold <86345081+beagold@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:03:40 +0200 Subject: [PATCH] Fix warning raised in aiohttp 3.9.4 regarding deprecated `content_transfer_encoding` argument (#1881) --- changes/1881.bugfix.md | 1 + hikari/internal/data_binding.py | 10 ++++--- tests/hikari/impl/test_interaction_server.py | 6 ++-- tests/hikari/internal/test_data_binding.py | 31 ++++++++++++++++---- 4 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 changes/1881.bugfix.md diff --git a/changes/1881.bugfix.md b/changes/1881.bugfix.md new file mode 100644 index 0000000000..4759723602 --- /dev/null +++ b/changes/1881.bugfix.md @@ -0,0 +1 @@ +Fix warning raised in aiohttp 3.9.4 when using `FormData` (most commonly, when uploading attachments) diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py index 6314a664dc..44c1fdd29f 100644 --- a/hikari/internal/data_binding.py +++ b/hikari/internal/data_binding.py @@ -95,7 +95,6 @@ _APPLICATION_OCTET_STREAM: typing.Final[str] = "application/octet-stream" _JSON_CONTENT_TYPE: typing.Final[str] = "application/json" -_BINARY: typing.Final[str] = "binary" _UTF_8: typing.Final[str] = "utf-8" default_json_dumps: JSONEncoder @@ -136,13 +135,16 @@ class URLEncodedFormBuilder: __slots__: typing.Sequence[str] = ("_fields", "_resources") def __init__(self) -> None: - self._fields: typing.List[typing.Tuple[str, typing.Union[str, bytes], typing.Optional[str]]] = [] + self._fields: typing.List[typing.Tuple[str, aiohttp.BytesPayload, typing.Optional[str]]] = [] self._resources: typing.List[typing.Tuple[str, files.Resource[files.AsyncReader]]] = [] def add_field( self, name: str, data: typing.Union[str, bytes], *, content_type: typing.Optional[str] = None ) -> None: - self._fields.append((name, data, content_type)) + if isinstance(data, str): + data = data.encode() + + self._fields.append((name, aiohttp.BytesPayload(data), content_type)) def add_resource(self, name: str, resource: files.Resource[files.AsyncReader]) -> None: self._resources.append((name, resource)) @@ -153,7 +155,7 @@ async def build( form = aiohttp.FormData() for field in self._fields: - form.add_field(field[0], field[1], content_type=field[2], content_transfer_encoding=_BINARY) + form.add_field(field[0], field[1], content_type=field[2]) for name, resource in self._resources: stream = await stack.enter_async_context(resource.stream(executor=executor)) diff --git a/tests/hikari/impl/test_interaction_server.py b/tests/hikari/impl/test_interaction_server.py index d8997701de..eab5ff2ca3 100644 --- a/tests/hikari/impl/test_interaction_server.py +++ b/tests/hikari/impl/test_interaction_server.py @@ -481,10 +481,10 @@ async def test_aiohttp_hook_when_files(self, mock_interaction_server: interactio await result.body.write(mock_writer) boundary = result.body.boundary.encode() - assert mock_writer.payload == ( + assert mock_writer.payload == bytearray( b"--" + boundary + b"""\r\nContent-Type: ooga/booga\r\nContent-Disposition: form-data; name="payload_json""" - b""""\r\nContent-Length: 5\r\n\r\nabody\r\n--""" + boundary + b"""\r\nContent-Type: text/plain\r\nConten""" - b"""t-Disposition: form-data; name="files[0]"; filename="meow.txt"\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""" + b""""\r\n\r\nabody\r\n--""" + boundary + b"""\r\nContent-Type: text/plain\r\nContent-Disposition: """ + b"""form-data; name="files[0]"; filename="meow.txt"\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""" b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""" b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""" b"""xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""" diff --git a/tests/hikari/internal/test_data_binding.py b/tests/hikari/internal/test_data_binding.py index a87eabc3bf..7462f56f27 100644 --- a/tests/hikari/internal/test_data_binding.py +++ b/tests/hikari/internal/test_data_binding.py @@ -43,9 +43,28 @@ def form_builder(self): return data_binding.URLEncodedFormBuilder() def test_add_field(self, form_builder): - form_builder.add_field("test_name", "test_data", content_type="mimetype") + class TestBytesPayload: + def __init__(self, value: bytes) -> None: + self.inner = value - assert form_builder._fields == [("test_name", "test_data", "mimetype")] + def __eq__(self, other: typing.Any) -> bool: + if not isinstance(other, TestBytesPayload): + return False + + return self.inner == other.inner + + def __repr__(self) -> str: + # Make it easier to debug future errors + return f"TestBytesPayload({self.inner!r})" + + with mock.patch.object(aiohttp, "BytesPayload", new=TestBytesPayload): + form_builder.add_field("test_name", "test_data", content_type="mimetype") + form_builder.add_field("test_name2", b"test_data2", content_type="mimetype2") + + assert form_builder._fields == [ + ("test_name", TestBytesPayload(b"test_data"), "mimetype"), + ("test_name2", TestBytesPayload(b"test_data2"), "mimetype2"), + ] def test_add_resource(self, form_builder): mock_resource = object() @@ -60,9 +79,11 @@ async def test_build(self, form_builder): resource2 = mock.Mock() stream1 = mock.Mock(filename="testing1", mimetype="text") stream2 = mock.Mock(filename="testing2", mimetype=None) + data1 = aiohttp.BytesPayload(b"data1") + data2 = aiohttp.BytesPayload(b"data2") mock_stack = mock.AsyncMock(enter_async_context=mock.AsyncMock(side_effect=[stream1, stream2])) executor = object() - form_builder._fields = [("test_name", "test_data", "mimetype"), ("test_name2", "test_data2", "mimetype2")] + form_builder._fields = [("test_name", data1, "mimetype"), ("test_name2", data2, "mimetype2")] form_builder._resources = [("aye", resource1), ("lmao", resource2)] with mock.patch.object(aiohttp, "FormData") as mock_form_class: @@ -75,8 +96,8 @@ async def test_build(self, form_builder): ) mock_form_class.return_value.add_field.assert_has_calls( [ - mock.call("test_name", "test_data", content_type="mimetype", content_transfer_encoding="binary"), - mock.call("test_name2", "test_data2", content_type="mimetype2", content_transfer_encoding="binary"), + mock.call("test_name", data1, content_type="mimetype"), + mock.call("test_name2", data2, content_type="mimetype2"), mock.call("aye", stream1, filename="testing1", content_type="text"), mock.call("lmao", stream2, filename="testing2", content_type="application/octet-stream"), ]