Repository: twisted/twisted

Issue:
mypy failing on trunk
```
mypy run-test: commands[0] | mypy --cache-dir=/Users/glyph/Projects/Twisted/.tox/mypy_cache --pretty src
src/twisted/web/http.py:257:16: error: Item "str" of "Message | str | Any" has no attribute "get_param" 
[union-attr]
            name = part.get_param("name", header="content-disposition")
                   ^~~~~~~~~~~~~~
src/twisted/web/http.py:260:19: error: Item "str" of "Message | str | Any" has no attribute "get_payload" 
[union-attr]
            payload = part.get_payload(decode=True)
                      ^~~~~~~~~~~~~~~~
src/twisted/web/http.py:261:16: error: Item "tuple[str | None, ...]" of
"str | Any | tuple[str | None, str | None, str]" has no attribute "encode"  [union-attr]
            result[name.encode("utf8")] = [payload]
                   ^~~~~~~~~~~
src/twisted/web/http.py:262:12: error: Incompatible return value type (got
"dict[bytes | Any, list[Message | bytes | Any]]", expected "dict[bytes, list[bytes]]")  [return-value]
        return result
               ^~~~~~
src/twisted/mail/test/test_bounce.py:93:26: error: Item "str" of "Any | str | Message" has no attribute
"get_payload"  [union-attr]
            self.assertEqual(parts[0].get_payload(), "Custom transcript\n")
                             ^~~~~~~~~~~~~~~~~~~~
src/twisted/mail/test/test_bounce.py:93:32: error: Invalid index type "int" for
"Message | str | list[Message | str] | Any"; expected type "str"  [index]
            self.assertEqual(parts[0].get_payload(), "Custom transcript\n")
                                   ^
src/twisted/mail/test/test_bounce.py:111:24: error: Item "str" of "Any | str | Message" has no attribute
"get_payload"  [union-attr]
            innerMessage = parts[1].get_payload()
                           ^~~~~~~~~~~~~~~~~~~~
src/twisted/mail/test/test_bounce.py:111:30: error: Invalid index type "int" for
"Message | str | list[Message | str] | Any"; expected type "str"  [index]
            innerMessage = parts[1].get_payload()
                                 ^
src/twisted/mail/test/test_bounce.py:116:26: error: Item "str" of "Any | str | Message" has no attribute
"get_payload"  [union-attr]
            self.assertEqual(innerMessage[0].get_payload() + "\n", messageText)
                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~
src/twisted/mail/test/test_bounce.py:116:26: error: Unsupported left operand type for + ("Message")  [operator]
            self.assertEqual(innerMessage[0].get_payload() + "\n", messageText)
                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/twisted/mail/test/test_bounce.py:116:26: error: No overload variant of "__add__" of "list" matches argument
type "str"  [operator]
            self.assertEqual(innerMessage[0].get_payload() + "\n", messageText)
                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/twisted/mail/test/test_bounce.py:116:26: note: Possible overload variants:
src/twisted/mail/test/test_bounce.py:116:26: note:     def __add__(self, list[Message | str], /) -> list[Message | str]
src/twisted/mail/test/test_bounce.py:116:26: note:     def [_S] __add__(self, list[_S], /) -> list[_S | Message | str]
src/twisted/mail/test/test_bounce.py:116:26: note: Left operand is of type "Any | Message | str | list[Message | str]"
src/twisted/mail/test/test_bounce.py:116:39: error: Invalid index type "int" for
"Any | Message | str | list[Message | str]"; expected type "str"  [index]
            self.assertEqual(innerMessage[0].get_payload() + "\n", messageText)
                                          ^
src/twisted/trial/test/test_script.py:531: error: Unused "type: ignore" comment  [unused-ignore]
                options.tracer.globaltrace,  # type: ignore[attr-defined]
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 13 errors in 3 files (checked 851 source files)
ERROR: InvocationError for command /Users/glyph/Projects/Twisted/.tox/mypy/bin/mypy --cache-dir=/Users/glyph/Projects/Twisted/.tox/mypy_cache --pretty src (exited with code 1)
```


Discussion:
filed #12114 so we can start tracking how often this happens

Resolving patch:
diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
--- a/src/twisted/web/http.py
+++ b/src/twisted/web/http.py
@@ -109,7 +109,7 @@
 import time
 import warnings
 from email import message_from_bytes
-from email.message import EmailMessage
+from email.message import EmailMessage, Message
 from io import BytesIO
 from typing import AnyStr, Callable, Dict, List, Optional, Tuple
 from urllib.parse import (
@@ -253,11 +253,16 @@ def _getMultiPartArgs(content: bytes, ctype: bytes) -> dict[bytes, list[bytes]]:
     if not msg.is_multipart():
         raise _MultiPartParseException("Not a multipart.")
 
-    for part in msg.get_payload():
-        name = part.get_param("name", header="content-disposition")
+    part: Message
+    # "per Python docs, a list of Message objects when is_multipart() is True,
+    # or a string when is_multipart() is False"
+    for part in msg.get_payload():  # type:ignore[assignment]
+        name: str | None = part.get_param(
+            "name", header="content-disposition"
+        )  # type:ignore[assignment]
         if not name:
             continue
-        payload = part.get_payload(decode=True)
+        payload: bytes = part.get_payload(decode=True)  # type:ignore[assignment]
         result[name.encode("utf8")] = [payload]
     return result
 


Test patch:
diff --git a/src/twisted/mail/test/test_bounce.py b/src/twisted/mail/test/test_bounce.py
--- a/src/twisted/mail/test/test_bounce.py
+++ b/src/twisted/mail/test/test_bounce.py
@@ -4,6 +4,7 @@
 
 import email.message
 import email.parser
+from email.message import Message
 from io import BytesIO, StringIO
 from typing import IO, AnyStr, Callable
 
@@ -89,8 +90,11 @@ def test_bounceMessageCustomTranscript(self) -> None:
         self.assertEqual(mess["From"], "postmaster@example.org")
         self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
         self.assertTrue(mess.is_multipart())
-        parts = mess.get_payload()
-        self.assertEqual(parts[0].get_payload(), "Custom transcript\n")
+        parts: list[Message] = mess.get_payload()  # type:ignore[assignment]
+        self.assertEqual(
+            parts[0].get_payload(),
+            "Custom transcript\n",
+        )
 
     def _bounceBigMessage(
         self, header: AnyStr, message: AnyStr, ioType: Callable[[AnyStr], IO[AnyStr]]
@@ -107,13 +111,14 @@ def _bounceBigMessage(
         self.assertEqual(mess["From"], "postmaster@example.org")
         self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
         self.assertTrue(mess.is_multipart())
-        parts = mess.get_payload()
-        innerMessage = parts[1].get_payload()
+        parts: list[Message] = mess.get_payload()  # type:ignore[assignment]
+        innerMessage: list[Message] = parts[1].get_payload()  # type:ignore[assignment]
         if isinstance(message, bytes):
             messageText = message.decode("utf-8")
         else:
             messageText = message
-        self.assertEqual(innerMessage[0].get_payload() + "\n", messageText)
+        pl: str = innerMessage[0].get_payload()  # type:ignore[assignment]
+        self.assertEqual(pl + "\n", messageText)
 
     def test_bounceBigMessage(self) -> None:
         """
diff --git a/src/twisted/trial/test/test_script.py b/src/twisted/trial/test/test_script.py
--- a/src/twisted/trial/test/test_script.py
+++ b/src/twisted/trial/test/test_script.py
@@ -528,7 +528,7 @@ def test_tracerInstalled(self) -> None:
         self.assertEqual(
             sys.gettrace(),
             # the globaltrace attribute of trace.Trace is undocumented
-            options.tracer.globaltrace,  # type: ignore[attr-defined]
+            options.tracer.globaltrace,
         )
 
     def test_coverdirDefault(self) -> None: