Repository: hikari-py/hikari
Pull request #1733 (2023-10-18 13:10:41)

Bump mypy from 1.5.1 to 1.6.1
Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/python/mypy/blob/master/CHANGELOG.md">mypy's changelog</a>.</em></p>
<blockquote>
<h1>Mypy Release Notes</h1>
<h2>Unreleased</h2>
<p>...</p>
<h4>Other Notable Changes and Fixes</h4>
<p>...</p>
<h4>Acknowledgements</h4>
<p>...</p>
<h2>Mypy 1.6</h2>
<p><a href="https://mypy-lang.blogspot.com/2023/10/mypy-16-released.html">Tuesday, 10 October 2023</a></p>
<p>We’ve just uploaded mypy 1.6 to the Python Package Index (<a href="https://pypi.org/project/mypy/">PyPI</a>). Mypy is a static type checker for Python. This release includes new features, performance improvements and bug fixes. You can install it as follows:</p>
<pre><code>python3 -m pip install -U mypy
</code></pre>
<p>You can read the full documentation for this release on <a href="http://mypy.readthedocs.io">Read the Docs</a>.</p>
<h4>Introduce Error Subcodes for Import Errors</h4>
<p>Mypy now uses the error code import-untyped if an import targets an installed library that doesn’t support static type checking, and no stub files are available. Other invalid imports produce the import-not-found error code. They both are subcodes of the import error code, which was previously used for both kinds of import-related errors.</p>
<p>Use --disable-error-code=import-untyped to only ignore import errors about installed libraries without stubs. This way mypy will still report errors about typos in import statements, for example.</p>
<p>If you use --warn-unused-ignore or --strict, mypy will complain if you use # type: ignore[import] to ignore an import error. You are expected to use one of the more specific error codes instead. Otherwise, ignoring the import error code continues to silence both errors.</p>
<p>This feature was contributed by Shantanu (PR <a href="https://redirect.github.com/python/mypy/pull/15840">15840</a>, PR <a href="https://redirect.github.com/python/mypy/pull/14740">14740</a>).</p>
<h4>Remove Support for Targeting Python 3.6 and Earlier</h4>
<p>Running mypy with --python-version 3.6, for example, is no longer supported. Python 3.6 hasn’t been properly supported by mypy for some time now, and this makes it explicit. This was contributed by Nikita Sobolev (PR <a href="https://redirect.github.com/python/mypy/pull/15668">15668</a>).</p>
<h4>Selective Filtering of --disallow-untyped-calls Targets</h4>
<p>Using --disallow-untyped-calls could be annoying when using libraries with missing type information, as mypy would generate many errors about code that uses the library. Now you can use --untyped-calls-exclude=acme, for example, to disable these errors about calls targeting functions defined in the acme package. Refer to the <a href="https://mypy.readthedocs.io/en/latest/command_line.html#cmdoption-mypy-untyped-calls-exclude">documentation</a> for more information.</p>
<p>This feature was contributed by Ivan Levkivskyi (PR <a href="https://redirect.github.com/python/mypy/pull/15845">15845</a>).</p>
<h4>Improved Type Inference between Callable Types</h4>
<p>Mypy now does a better job inferring type variables inside arguments of callable types. For example, this code fragment now type checks correctly:</p>
<pre lang="python"><code>def f(c: Callable[[T, S], None]) -&gt; Callable[[str, T, S], None]: ...
def g(*x: int) -&gt; None: ...
<p>&lt;/tr&gt;&lt;/table&gt;
</code></pre></p>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/python/mypy/commit/9b891fe5a101ecbb818f3f16641ab909f289ba04"><code>9b891fe</code></a> Remove +dev from version</li>
<li><a href="https://github.com/python/mypy/commit/6b6504eb8a96fa6a9c7b8f034803eb9a0444fe86"><code>6b6504e</code></a> Fix crash on ParamSpec unification (for real) (<a href="https://redirect.github.com/python/mypy/issues/16259">#16259</a>)</li>
<li><a href="https://github.com/python/mypy/commit/eb81e63e9dec4dd4c75b307871d1ef9b3e350838"><code>eb81e63</code></a> Fix crash on ParamSpec unification (<a href="https://redirect.github.com/python/mypy/issues/16251">#16251</a>)</li>
<li><a href="https://github.com/python/mypy/commit/45f7a12e558e4a48446af3b36494dcb4045c1028"><code>45f7a12</code></a> Add +dev to version</li>
<li><a href="https://github.com/python/mypy/commit/ac2d56f9145a4e15e7c0d1fbcd9a466ad0cec9a1"><code>ac2d56f</code></a> Remove +dev from version</li>
<li><a href="https://github.com/python/mypy/commit/5b488ab8ad82ce11388a63367a60e1443f359f04"><code>5b488ab</code></a> Optimize Unpack for failures (<a href="https://redirect.github.com/python/mypy/issues/15967">#15967</a>)</li>
<li><a href="https://github.com/python/mypy/commit/4c963c9973d319e145be540ce40637d1f43506cd"><code>4c963c9</code></a> Make PEP 695 constructs give a reasonable error message (<a href="https://redirect.github.com/python/mypy/issues/16013">#16013</a>)</li>
<li><a href="https://github.com/python/mypy/commit/e78df552ebf84c3e89fbc7dab0cbc459a7ca306c"><code>e78df55</code></a> Match note error codes to import error codes (<a href="https://redirect.github.com/python/mypy/issues/16004">#16004</a>)</li>
<li><a href="https://github.com/python/mypy/commit/d3766331e89e4cf9f58d7d094b8df10acf63a0e5"><code>d376633</code></a> Fix mypyc regression with pretty (<a href="https://redirect.github.com/python/mypy/issues/16124">#16124</a>)</li>
<li><a href="https://github.com/python/mypy/commit/00cba138a382f8e64b3bbc291c9a4b2ba25a6cb0"><code>00cba13</code></a> Clear cache when adding --new-type-inference (<a href="https://redirect.github.com/python/mypy/issues/16059">#16059</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/python/mypy/compare/v1.5.1...v1.6.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mypy&package-manager=pip&previous-version=1.5.1&new-version=1.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ce355fde..7cf52380 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -179,7 +179,7 @@ jobs:
           python-version: 3.9
 
       - name: Setup node
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
 
       - name: Build documentation
         run: |
diff --git a/changes/1661.feature.md b/changes/1661.feature.md
new file mode 100644
index 00000000..793ed543
--- /dev/null
+++ b/changes/1661.feature.md
@@ -0,0 +1,1 @@
+Allow class listeners
diff --git a/dev-requirements/formatting.txt b/dev-requirements/formatting.txt
index d5af1ce5..996ed1b9 100644
--- a/dev-requirements/formatting.txt
+++ b/dev-requirements/formatting.txt
@@ -1,2 +1,2 @@
-black==23.9.1
+black==23.10.1
 isort==5.12.0
diff --git a/dev-requirements/mypy.txt b/dev-requirements/mypy.txt
index d0c68f7f..e38deb7c 100644
--- a/dev-requirements/mypy.txt
+++ b/dev-requirements/mypy.txt
@@ -1,1 +1,1 @@
-mypy==1.5.1
+mypy==1.6.1
diff --git a/dev-requirements/pyright.txt b/dev-requirements/pyright.txt
index 8059f588..f3a3b12b 100644
--- a/dev-requirements/pyright.txt
+++ b/dev-requirements/pyright.txt
@@ -1,1 +1,1 @@
-pyright==1.1.329
+pyright==1.1.333
diff --git a/dev-requirements/towncrier.txt b/dev-requirements/towncrier.txt
index 98573b5b..65670dc4 100644
--- a/dev-requirements/towncrier.txt
+++ b/dev-requirements/towncrier.txt
@@ -1,1 +1,1 @@
-towncrier==23.6.0
+towncrier==23.10.0
diff --git a/hikari/impl/event_manager_base.py b/hikari/impl/event_manager_base.py
index 8a4e5f60..f944890d 100644
--- a/hikari/impl/event_manager_base.py
+++ b/hikari/impl/event_manager_base.py
@@ -421,7 +421,9 @@ def consume_raw_event(
     def subscribe(
         self, event_type: typing.Type[typing.Any], callback: event_manager_.CallbackT[typing.Any], *, _nested: int = 0
     ) -> None:
-        if not inspect.iscoroutinefunction(callback):
+        if not (
+            inspect.iscoroutinefunction(callback) or inspect.iscoroutinefunction(getattr(callback, "__call__", None))
+        ):
             raise TypeError("Cannot subscribe a non-coroutine function callback")
 
         # `_nested` is used to show the correct source code snippet if an intent
@@ -620,8 +622,11 @@ async def _invoke_callback(
         try:
             await callback(event)
         except Exception as ex:
-            # Skip the first frame in logs, we don't care for it.
-            trio = type(ex), ex, ex.__traceback__.tb_next if ex.__traceback__ is not None else None
+            # Skip the first frame in logs if it exists, as it means it wasn't our fault
+            trio: typing.Union[
+                typing.Tuple[typing.Type[Exception], Exception, typing.Optional[types.TracebackType]], Exception
+            ]
+            trio = (type(ex), ex, ex.__traceback__.tb_next) if ex.__traceback__ else ex
 
             if base_events.is_no_recursive_throw_event(event):
                 _LOGGER.error(
diff --git a/hikari/internal/attrs_extensions.py b/hikari/internal/attrs_extensions.py
index 03c3b31f..8370eba3 100644
--- a/hikari/internal/attrs_extensions.py
+++ b/hikari/internal/attrs_extensions.py
@@ -37,7 +37,7 @@
 
 import attrs
 
-ModelT = typing.TypeVar("ModelT")
+ModelT = typing.TypeVar("ModelT", bound=attrs.AttrsInstance)
 SKIP_DEEP_COPY: typing.Final[str] = "skip_deep_copy"
 
 _DEEP_COPIERS: typing.MutableMapping[
@@ -60,7 +60,7 @@ def invalidate_deep_copy_cache() -> None:
 
 
 def get_fields_definition(
-    cls: type,
+    cls: typing.Type[attrs.AttrsInstance],
 ) -> typing.Tuple[
     typing.Sequence[typing.Tuple[attrs.Attribute[typing.Any], str]], typing.Sequence[attrs.Attribute[typing.Any]]
 ]:
@@ -68,7 +68,7 @@ def get_fields_definition(
 
     Parameters
     ----------
-    cls : typing.Type[ModelT]
+    cls : typing.Type[attrs.AttrsInstance]
         The attrs class to get the fields definition for.
 
     Returns
@@ -79,7 +79,10 @@ def get_fields_definition(
     init_results: typing.List[typing.Tuple[attrs.Attribute[typing.Any], str]] = []
     non_init_results: typing.List[attrs.Attribute[typing.Any]] = []
 
-    for field in attrs.fields(cls):
+    # Mypy has a bug where it will always report
+    # "Argument 1 to "fields" has incompatible type "Type[AttrsInstance]"; expected an attrs class"
+    # even if the type is correct.
+    for field in attrs.fields(cls):  # type: ignore[misc]
         if field.init:
             key_word = field.name[1:] if field.name.startswith("_") else field.name
             init_results.append((field, key_word))
@@ -107,8 +110,8 @@ def generate_shallow_copier(cls: typing.Type[ModelT]) -> typing.Callable[[ModelT
     from hikari.internal import ux
 
     kwargs, setters = get_fields_definition(cls)
-    kwargs = ",".join(f"{kwarg}=m.{attrsibute.name}" for attrsibute, kwarg in kwargs)
-    setters = ";".join(f"r.{attrsibute.name}=m.{attrsibute.name}" for attrsibute in setters) + ";" if setters else ""
+    kwargs = ",".join(f"{kwarg}=m.{attribute.name}" for attribute, kwarg in kwargs)
+    setters = ";".join(f"r.{attribute.name}=m.{attribute.name}" for attribute in setters) + ";" if setters else ""
     code = f"def copy(m):r=cls({kwargs});{setters}return r"
     globals_ = {"cls": cls}
     _LOGGER.log(ux.TRACE, "generating shallow copy function for %r: %r", cls, code)


Test patch:
diff --git a/dev-requirements/pytest.txt b/dev-requirements/pytest.txt
index eca23ade..be98f135 100644
--- a/dev-requirements/pytest.txt
+++ b/dev-requirements/pytest.txt
@@ -1,7 +1,7 @@
 # mock in the stdlib changes between versions of Python, so are not consistent in behaviour like the backport is
 mock==5.1.0
 
-pytest==7.4.2
+pytest==7.4.3
 pytest-asyncio==0.21.1
 pytest-cov==4.1.0
 pytest-randomly==3.15.0
diff --git a/tests/hikari/impl/test_event_manager_base.py b/tests/hikari/impl/test_event_manager_base.py
index bd242941..ebba1c2e 100644
--- a/tests/hikari/impl/test_event_manager_base.py
+++ b/tests/hikari/impl/test_event_manager_base.py
@@ -601,6 +601,18 @@ async def test_handle_dispatch_invokes_when_consumer_not_enabled(self, event_man
         consumer.callback.assert_not_called()
         error_handler.assert_not_called()
 
+    def test_subscribe_when_class_call(self, event_manager):
+        class Foo:
+            async def __call__(self) -> None:
+                ...
+
+        foo = Foo()
+        event_manager._check_event = mock.Mock()
+
+        event_manager.subscribe(member_events.MemberCreateEvent, foo)
+
+        assert event_manager._listeners[member_events.MemberCreateEvent] == [foo]
+
     def test_subscribe_when_callback_is_not_coroutine(self, event_manager):
         def test():
             ...