diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cf523801e..88b80e87a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.8, 3.9, "3.10", 3.11] + python-version: [3.8, 3.9, "3.10", 3.11, 3.12] runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 1ad275816e..98645aeb0d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Gateway APIs. Built on good intentions and the hope that it will be extendable and reusable, rather than an obstacle for future development. -Python 3.8, 3.9, 3.10 and 3.11 are currently supported. +Python 3.8, 3.9, 3.10, 3.11 and 3.12 are currently supported. ## Installation diff --git a/changes/1357.feature.md b/changes/1357.feature.md new file mode 100644 index 0000000000..0593ed2571 --- /dev/null +++ b/changes/1357.feature.md @@ -0,0 +1 @@ +Add Python 3.12 support. diff --git a/hikari/impl/shard.py b/hikari/impl/shard.py index 7bbe4241a9..48f3b9b4ae 100644 --- a/hikari/impl/shard.py +++ b/hikari/impl/shard.py @@ -219,7 +219,8 @@ def _handle_other_message(self, message: aiohttp.WSMessage, /) -> typing.NoRetur close_code = int(message.data) can_reconnect = close_code < 4000 or close_code in _RECONNECTABLE_CLOSE_CODES - raise errors.GatewayServerClosedConnectionError(message.extra, close_code, can_reconnect) + # str(message.extra) is used to cast the possible None to a string + raise errors.GatewayServerClosedConnectionError(str(message.extra), close_code, can_reconnect) if message.type == aiohttp.WSMsgType.CLOSING or message.type == aiohttp.WSMsgType.CLOSED: # May be caused by the server shutting us down. diff --git a/hikari/internal/fast_protocol.py b/hikari/internal/fast_protocol.py index a313578039..2e9f3b6f64 100644 --- a/hikari/internal/fast_protocol.py +++ b/hikari/internal/fast_protocol.py @@ -32,7 +32,7 @@ from typing_extensions import Self _Protocol: FastProtocolChecking = NotImplemented -_IGNORED_ATTRS = typing.EXCLUDED_ATTRIBUTES + ["__qualname__", "__slots__"] +_IGNORED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | {"__qualname__", "__slots__"} def _check_if_ignored(name: str) -> bool: diff --git a/requirements.txt b/requirements.txt index 1526161d40..44ae1bfe00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp~=3.8 +aiohttp~=3.9 attrs~=23.1 colorlog~=6.7 multidict~=6.0 diff --git a/setup.py b/setup.py index 6ec507f638..e70e11c81a 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ def parse_requirements_file(path): maintainer_email=metadata.email, license=metadata.license, url=metadata.url, - python_requires=">=3.8.0,<3.12", + python_requires=">=3.8.0,<3.13", packages=setuptools.find_namespace_packages(include=["hikari*"]), entry_points={"console_scripts": ["hikari = hikari.cli:main"]}, install_requires=parse_requirements_file("requirements.txt"), @@ -95,6 +95,7 @@ def parse_requirements_file(path): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Communications :: Chat", "Topic :: Internet :: WWW/HTTP", diff --git a/tests/hikari/impl/test_event_manager_base.py b/tests/hikari/impl/test_event_manager_base.py index ebba1c2ec6..89935f66d2 100644 --- a/tests/hikari/impl/test_event_manager_base.py +++ b/tests/hikari/impl/test_event_manager_base.py @@ -572,6 +572,10 @@ async def test_handle_dispatch_ignores_cancelled_errors(self, event_manager, eve @pytest.mark.asyncio() async def test_handle_dispatch_handles_exceptions(self, event_manager, event_loop): + mock_task = mock.Mock() + # On Python 3.12+ Asyncio uses this to get the task's context if set to call the + # error handler in. We want to avoid for this test for simplicity. + mock_task.get_context.return_value = None event_manager._enabled_for_consumer = mock.Mock(return_value=True) exc = Exception("aaaa!") consumer = mock.Mock(callback=mock.AsyncMock(side_effect=exc)) @@ -580,12 +584,12 @@ async def test_handle_dispatch_handles_exceptions(self, event_manager, event_loo shard = object() pl = {"i like": "cats"} - with mock.patch.object(asyncio, "current_task") as current_task: + with mock.patch.object(asyncio, "current_task", return_value=mock_task): await event_manager._handle_dispatch(consumer, shard, pl) error_handler.assert_called_once_with( event_loop, - {"exception": exc, "message": "Exception occurred in raw event dispatch conduit", "task": current_task()}, + {"exception": exc, "message": "Exception occurred in raw event dispatch conduit", "task": mock_task}, ) @pytest.mark.asyncio()