Repository: hikari-py/hikari
Pull request #1945 (2024-06-22 11:44:43)

Add `title` and `description` fields to `Attachment`
### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.

### Related issues
None



Patch:
diff --git a/changes/1945.feature.md b/changes/1945.feature.md
new file mode 100644
index 00000000..aa826bd9
--- /dev/null
+++ b/changes/1945.feature.md
@@ -0,0 +1,1 @@
+Add `title` and `description` fields to `Attachment`.
diff --git a/changes/1946.bugfix.md b/changes/1946.bugfix.md
new file mode 100644
index 00000000..820d1f04
--- /dev/null
+++ b/changes/1946.bugfix.md
@@ -0,0 +1,1 @@
+Fix incorrect cleanup when failing to create request with a web reader
diff --git a/dev-requirements/coverage.txt b/dev-requirements/coverage.txt
index 25c0a423..e73498bf 100644
--- a/dev-requirements/coverage.txt
+++ b/dev-requirements/coverage.txt
@@ -1,1 +1,1 @@
-coverage[toml]==7.5.3
+coverage[toml]==7.5.4
diff --git a/dev-requirements/mypy.txt b/dev-requirements/mypy.txt
index a0dcb92d..6dd43248 100644
--- a/dev-requirements/mypy.txt
+++ b/dev-requirements/mypy.txt
@@ -1,1 +1,1 @@
-mypy==1.10.0
+mypy==1.10.1
diff --git a/dev-requirements/pyright.txt b/dev-requirements/pyright.txt
index 40f494b0..27c9148f 100644
--- a/dev-requirements/pyright.txt
+++ b/dev-requirements/pyright.txt
@@ -1,1 +1,1 @@
-pyright==1.1.368
+pyright==1.1.369
diff --git a/hikari/files.py b/hikari/files.py
index 3a0941b6..2b2c7398 100644
--- a/hikari/files.py
+++ b/hikari/files.py
@@ -616,37 +616,42 @@ async def __aenter__(self) -> WebReader:
         ctx = client_session.request(method, self._web_resource.url, raise_for_status=False)
 
         try:
+            # Double try is needed to avoid calling __aexit__ if the __aenter__ raised the error
             resp: aiohttp.ClientResponse = await ctx.__aenter__()
 
-            if 200 <= resp.status < 400:
-                mimetype = None
-                filename = self._web_resource.filename
-
-                if resp.content_disposition is not None:
-                    mimetype = resp.content_disposition.type
-
-                if mimetype is None:
-                    mimetype = resp.content_type
-
-                self._client_response_ctx = ctx
-                self._client_session = client_session
-
-                return WebReader(
-                    stream=resp.content,
-                    url=str(resp.real_url),
-                    status=resp.status,
-                    reason=str(resp.reason),
-                    filename=filename,
-                    charset=resp.charset,
-                    mimetype=mimetype,
-                    size=resp.content_length,
-                    head_only=self._head_only,
-                )
-            else:
-                raise await net.generate_error_response(resp)
-
-        except Exception as ex:
-            await ctx.__aexit__(type(ex), ex, ex.__traceback__)
+            try:
+                if 200 <= resp.status < 400:
+                    mimetype = None
+                    filename = self._web_resource.filename
+
+                    if resp.content_disposition is not None:
+                        mimetype = resp.content_disposition.type
+
+                    if mimetype is None:
+                        mimetype = resp.content_type
+
+                    self._client_response_ctx = ctx
+                    self._client_session = client_session
+
+                    return WebReader(
+                        stream=resp.content,
+                        url=str(resp.real_url),
+                        status=resp.status,
+                        reason=str(resp.reason),
+                        filename=filename,
+                        charset=resp.charset,
+                        mimetype=mimetype,
+                        size=resp.content_length,
+                        head_only=self._head_only,
+                    )
+                else:
+                    raise await net.generate_error_response(resp)
+
+            except Exception as ex:
+                await ctx.__aexit__(type(ex), ex, ex.__traceback__)
+                raise
+
+        except Exception:
             await client_session.close()
             raise
 
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index 409c3920..4ddb940b 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -2968,6 +2968,8 @@ def _deserialize_message_attachment(self, payload: data_binding.JSONObject) -> m
         return message_models.Attachment(
             id=snowflakes.Snowflake(payload["id"]),
             filename=payload["filename"],
+            title=payload.get("title"),
+            description=payload.get("description"),
             media_type=payload.get("content_type"),
             size=int(payload["size"]),
             url=payload["url"],
diff --git a/hikari/messages.py b/hikari/messages.py
index 5b53f7c7..9184c9ee 100644
--- a/hikari/messages.py
+++ b/hikari/messages.py
@@ -213,7 +213,17 @@ class Attachment(snowflakes.Unique, files.WebResource):
     """The source URL of file."""
 
     filename: str = attrs.field(hash=False, eq=False, repr=True)
-    """The name of the file."""
+    """The filename of the file."""
+
+    title: typing.Optional[str] = attrs.field(hash=False, eq=False, repr=True)
+    """The title of the file.
+
+    This will be the original filename of the attachment if it contained
+    non-unicode characters.
+    """
+
+    description: typing.Optional[str] = attrs.field(hash=False, eq=False, repr=True)
+    """The description of the file."""
 
     media_type: typing.Optional[str] = attrs.field(hash=False, eq=False, repr=True)
     """The media type of the file."""


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index e5038f9d..c3d195af 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -5650,6 +5650,8 @@ def attachment_payload(self):
         return {
             "id": "690922406474154014",
             "filename": "IMG.jpg",
+            "title": "IMGA",
+            "description": "description",
             "content_type": "image/png",
             "size": 660521,
             "url": "https://somewhere.com/attachments/123/456/IMG.jpg",
@@ -5719,6 +5721,8 @@ def test__deserialize_message_attachment(self, entity_factory_impl, attachment_p
 
         assert attachment.id == 690922406474154014
         assert attachment.filename == "IMG.jpg"
+        assert attachment.title == "IMGA"
+        assert attachment.description == "description"
         assert attachment.size == 660521
         assert attachment.media_type == "image/png"
         assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
@@ -5741,6 +5745,8 @@ def test__deserialize_message_attachment_with_null_fields(self, entity_factory_i
         assert isinstance(attachment, message_models.Attachment)
 
     def test__deserialize_message_attachment_with_unset_fields(self, entity_factory_impl, attachment_payload):
+        del attachment_payload["title"]
+        del attachment_payload["description"]
         del attachment_payload["content_type"]
         del attachment_payload["height"]
         del attachment_payload["width"]
@@ -5750,6 +5756,8 @@ def test__deserialize_message_attachment_with_unset_fields(self, entity_factory_
 
         attachment = entity_factory_impl._deserialize_message_attachment(attachment_payload)
 
+        assert attachment.title is None
+        assert attachment.description is None
         assert attachment.media_type is None
         assert attachment.height is None
         assert attachment.width is None
@@ -5967,6 +5975,8 @@ def test_deserialize_message(
         attachment = message.attachments[0]
         assert attachment.id == 690922406474154014
         assert attachment.filename == "IMG.jpg"
+        assert attachment.title == "IMGA"
+        assert attachment.description == "description"
         assert attachment.size == 660521
         assert attachment.url == "https://somewhere.com/attachments/123/456/IMG.jpg"
         assert attachment.proxy_url == "https://media.somewhere.com/attachments/123/456/IMG.jpg"
diff --git a/tests/hikari/test_messages.py b/tests/hikari/test_messages.py
index 027697c8..781fa99f 100644
--- a/tests/hikari/test_messages.py
+++ b/tests/hikari/test_messages.py
@@ -39,6 +39,8 @@ def test_str_operator(self):
         attachment = messages.Attachment(
             id=123,
             filename="super_cool_file.cool",
+            title="other title",
+            description="description!",
             media_type="image/png",
             height=222,
             width=555,