diff --git a/.github/workflows/docs-checks.yml b/.github/workflows/docs-checks.yml index 85bfc8267d..65b1b68338 100644 --- a/.github/workflows/docs-checks.yml +++ b/.github/workflows/docs-checks.yml @@ -8,7 +8,7 @@ on: - "requirements/**" - "*.toml" - "*.py" - branches: [master] + branches: [ master ] pull_request: paths: - "discord/**" @@ -40,7 +40,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "requirements/docs.txt" check-latest: true diff --git a/.github/workflows/docs-json-export.yml b/.github/workflows/docs-json-export.yml index d9e5904273..f4f1f8d5a7 100644 --- a/.github/workflows/docs-json-export.yml +++ b/.github/workflows/docs-json-export.yml @@ -18,7 +18,7 @@ jobs: uses: actions/setup-python@v6 id: setup-python with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "requirements/docs.txt" check-latest: true @@ -45,7 +45,7 @@ jobs: run: | head -n 40 docs.json || tail -n 40 docs.json - name: Output artifact ID - run: | + run: | echo "artifact-id=${{ steps.artifact-upload.outputs.artifact-id }}" >> $GITHUB_OUTPUT echo "artifact-url=${{ steps.artifact-upload.outputs.artifact-url }}" >> $GITHUB_OUTPUT echo "::notice::Artifact uploaded: ${{ steps.artifact-upload.outputs.artifact-url }}" diff --git a/.github/workflows/docs-localization-download.yml b/.github/workflows/docs-localization-download.yml index 54b44e69a2..2cf78f91bf 100644 --- a/.github/workflows/docs-localization-download.yml +++ b/.github/workflows/docs-localization-download.yml @@ -21,7 +21,7 @@ jobs: - name: "Install Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "requirements/_locale.txt" - name: "Install Dependencies" @@ -69,7 +69,7 @@ jobs: pr: name: "PR operations" - needs: [download] + needs: [ download ] runs-on: ubuntu-latest environment: translations steps: diff --git a/.github/workflows/docs-localization-upload.yml b/.github/workflows/docs-localization-upload.yml index 169e853cc3..9117c19be2 100644 --- a/.github/workflows/docs-localization-upload.yml +++ b/.github/workflows/docs-localization-upload.yml @@ -5,11 +5,11 @@ on: paths: - "discord/**" - "docs/**" - branches: [master] + branches: [ master ] workflow_dispatch: schedule: - cron: "0 0 * * 1" - + permissions: write-all jobs: @@ -26,7 +26,7 @@ jobs: - name: "Install Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "requirements/_locale.txt" - name: "Install Dependencies" diff --git a/.github/workflows/lib-checks.yml b/.github/workflows/lib-checks.yml index 0c622408bf..ea012693ee 100644 --- a/.github/workflows/lib-checks.yml +++ b/.github/workflows/lib-checks.yml @@ -36,7 +36,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "pyproject.toml" - name: "Install dependencies" @@ -56,7 +56,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "pyproject.toml" - name: "Install dependencies" @@ -74,7 +74,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "pyproject.toml" - name: "Install dependencies" @@ -98,7 +98,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "pyproject.toml" - name: "Install dependencies" @@ -124,7 +124,7 @@ jobs: - name: "Setup Python" uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "pyproject.toml" - name: "Install dependencies" @@ -141,7 +141,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] - python-version: [ "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] steps: - name: "Checkout Repository" uses: actions/checkout@v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02231fb948..b094ab904d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -136,7 +136,7 @@ jobs: id: python-setup uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" cache: "pip" cache-dependency-path: "requirements/_release.txt" - name: "Install Release Dependencies" diff --git a/.readthedocs.yml b/.readthedocs.yml index ee1beb81d4..54236d5039 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,10 +1,10 @@ version: 2 -formats: [] +formats: [ ] build: os: ubuntu-22.04 tools: - python: "3.13" + python: "3.14" sphinx: configuration: docs/conf.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4209b0dbb0..84d717f6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ These changes are available on the `master` branch, but have not yet been releas ### Added +- Support for **Python 3.14**. + ([#2948](https://github.com/Pycord-Development/pycord/pull/2948)) + ### Changed ### Fixed diff --git a/README.rst b/README.rst index fc7e038cb9..7ca0da1801 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Pycord is a modern, easy to use, feature-rich, and async ready API wrapper for D Note ---- -Pycord supports Python ``3.10`` - ``3.13`` +Pycord supports Python ``3.10`` - ``3.14`` Key Features ------------ diff --git a/discord/__main__.py b/discord/__main__.py index ed34bdf42a..b9b995500a 100644 --- a/discord/__main__.py +++ b/discord/__main__.py @@ -28,7 +28,6 @@ import platform import sys from pathlib import Path -from typing import Tuple import aiohttp @@ -352,7 +351,7 @@ def add_newcog_args(subparser: argparse._SubParsersAction) -> None: ) -def parse_args() -> Tuple[argparse.ArgumentParser, argparse.Namespace]: +def parse_args() -> tuple[argparse.ArgumentParser, argparse.Namespace]: parser = argparse.ArgumentParser( prog="discord", description="Tools for helping with Pycord" ) diff --git a/discord/abc.py b/discord/abc.py index e7a585b9ae..ec6eb415cb 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -28,13 +28,11 @@ import asyncio import copy import time +from collections.abc import Callable, Iterable, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, - Iterable, Protocol, - Sequence, TypeVar, Union, overload, diff --git a/discord/audit_logs.py b/discord/audit_logs.py index b2f6a72393..e944149fd6 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -26,8 +26,9 @@ from __future__ import annotations import datetime +from collections.abc import Callable, Generator from functools import cached_property -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generator, TypeVar +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar from . import enums, utils from .asset import Asset diff --git a/discord/backoff.py b/discord/backoff.py index 819c7d3cee..078f14e218 100644 --- a/discord/backoff.py +++ b/discord/backoff.py @@ -27,7 +27,8 @@ import random import time -from typing import Callable, Generic, Literal, TypeVar, overload +from collections.abc import Callable +from typing import Generic, Literal, TypeVar, overload T = TypeVar("T", bool, Literal[True], Literal[False]) diff --git a/discord/bot.py b/discord/bot.py index 5ba5f9b5df..1f6d52e220 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -34,14 +34,11 @@ import sys import traceback from abc import ABC, abstractmethod +from collections.abc import Callable, Coroutine, Generator, Mapping from typing import ( TYPE_CHECKING, Any, - Callable, - Coroutine, - Generator, Literal, - Mapping, TypeVar, ) diff --git a/discord/channel.py b/discord/channel.py index 2a6d5182ac..4714093e32 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -26,14 +26,11 @@ from __future__ import annotations import datetime +from collections.abc import Callable, Iterable, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, - Iterable, - Mapping, NamedTuple, - Sequence, TypeVar, overload, ) diff --git a/discord/client.py b/discord/client.py index 211532d68d..3bc99d533e 100644 --- a/discord/client.py +++ b/discord/client.py @@ -30,14 +30,11 @@ import signal import sys import traceback +from collections.abc import Callable, Coroutine, Generator, Sequence from types import TracebackType from typing import ( TYPE_CHECKING, Any, - Callable, - Coroutine, - Generator, - Sequence, TypeVar, ) @@ -70,7 +67,13 @@ from .threads import Thread from .ui.view import BaseView from .user import ClientUser, User -from .utils import _D, _FETCHABLE, MISSING, warn_if_voice_dependencies_missing +from .utils import ( + _D, + _FETCHABLE, + MISSING, + _get_event_loop, + warn_if_voice_dependencies_missing, +) from .webhook import Webhook from .widget import Widget @@ -147,7 +150,7 @@ class Client: loop: Optional[:class:`asyncio.AbstractEventLoop`] The :class:`asyncio.AbstractEventLoop` to use for asynchronous operations. Defaults to ``None``, in which case the default event loop is used via - :func:`asyncio.get_event_loop()`. + :func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`. connector: Optional[:class:`aiohttp.BaseConnector`] The connector to use for connection pooling. proxy: Optional[:class:`str`] @@ -245,7 +248,7 @@ def __init__( # self.ws is set in the connect method self.ws: DiscordWebSocket = None # type: ignore self.loop: asyncio.AbstractEventLoop = ( - asyncio.get_event_loop() if loop is None else loop + _get_event_loop() if loop is None else loop ) self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = ( {} diff --git a/discord/cog.py b/discord/cog.py index 5fbe6e61a4..d514e1febf 100644 --- a/discord/cog.py +++ b/discord/cog.py @@ -31,18 +31,16 @@ import pathlib import sys import types -from collections.abc import Generator, Mapping +from collections.abc import Callable, Generator, Mapping from typing import ( TYPE_CHECKING, Any, - Callable, ClassVar, + TypeGuard, TypeVar, overload, ) -from typing_extensions import TypeGuard - import discord.utils from . import errors diff --git a/discord/commands/context.py b/discord/commands/context.py index e97f19fa0f..b7206d0946 100644 --- a/discord/commands/context.py +++ b/discord/commands/context.py @@ -32,7 +32,7 @@ from discord.webhook.async_ import Webhook if TYPE_CHECKING: - from typing import Awaitable, Callable + from collections.abc import Awaitable, Callable from typing_extensions import ParamSpec diff --git a/discord/commands/core.py b/discord/commands/core.py index 0f721f15a0..10c2c2b386 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -33,13 +33,11 @@ import sys import types from collections import OrderedDict +from collections.abc import Callable, Coroutine, Generator from enum import Enum from typing import ( TYPE_CHECKING, Any, - Callable, - Coroutine, - Generator, Generic, TypeVar, Union, @@ -72,7 +70,7 @@ if sys.version_info >= (3, 11): from typing import Annotated, Literal, get_args, get_origin else: - from typing_extensions import Annotated, Literal, get_args, get_origin + from typing import Annotated, Literal, get_args, get_origin __all__ = ( "_BaseCommand", @@ -90,7 +88,9 @@ ) if TYPE_CHECKING: - from typing_extensions import Concatenate, ParamSpec + from typing import Concatenate + + from typing_extensions import ParamSpec from .. import Permissions from ..cog import Cog diff --git a/discord/commands/options.py b/discord/commands/options.py index 6567d0c4e7..a7d7d682ce 100644 --- a/discord/commands/options.py +++ b/discord/commands/options.py @@ -35,7 +35,6 @@ Any, Literal, Optional, - Type, TypeVar, Union, get_args, @@ -72,22 +71,22 @@ from ..user import User InputType = Union[ - Type[str], - Type[bool], - Type[int], - Type[float], - Type[GuildChannel], - Type[Thread], - Type[Member], - Type[User], - Type[Attachment], - Type[Role], - Type[Mentionable], + type[str], + type[bool], + type[int], + type[float], + type[GuildChannel], + type[Thread], + type[Member], + type[User], + type[Attachment], + type[Role], + type[Mentionable], SlashCommandOptionType, Converter, - Type[Converter], - Type[Enum], - Type[DiscordEnum], + type[Converter], + type[Enum], + type[DiscordEnum], ] AutocompleteReturnType = Union[ diff --git a/discord/commands/permissions.py b/discord/commands/permissions.py index daf633b05a..c1ed4f481e 100644 --- a/discord/commands/permissions.py +++ b/discord/commands/permissions.py @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. """ -from typing import Callable +from collections.abc import Callable from ..enums import InteractionContextType from ..permissions import Permissions diff --git a/discord/components.py b/discord/components.py index d6e1eb89a8..cc1d2f76ac 100644 --- a/discord/components.py +++ b/discord/components.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, ClassVar, Iterator, TypeVar, overload +from collections.abc import Iterator +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, overload from .asset import AssetMixin from .colour import Colour diff --git a/discord/embeds.py b/discord/embeds.py index fbdf35c619..31bdd959eb 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -26,7 +26,8 @@ from __future__ import annotations import datetime -from typing import TYPE_CHECKING, Any, Mapping, TypeVar +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar from . import utils from .colour import Colour diff --git a/discord/emoji.py b/discord/emoji.py index 39a224b18a..e694e846b9 100644 --- a/discord/emoji.py +++ b/discord/emoji.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator, Literal +from collections.abc import Iterator +from typing import TYPE_CHECKING, Any, Literal from .asset import Asset, AssetMixin from .partial_emoji import PartialEmoji, _EmojiTag diff --git a/discord/ext/bridge/core.py b/discord/ext/bridge/core.py index e61daa576f..e2c40299ad 100644 --- a/discord/ext/bridge/core.py +++ b/discord/ext/bridge/core.py @@ -26,8 +26,8 @@ from __future__ import annotations import inspect -from collections.abc import Iterator -from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Callable, Iterator +from typing import TYPE_CHECKING, Any import discord.commands.options from discord import ( diff --git a/discord/ext/commands/_types.py b/discord/ext/commands/_types.py index d3f0336471..643dfd7ab0 100644 --- a/discord/ext/commands/_types.py +++ b/discord/ext/commands/_types.py @@ -23,7 +23,8 @@ DEALINGS IN THE SOFTWARE. """ -from typing import TYPE_CHECKING, Any, Callable, Coroutine, TypeVar, Union +from collections.abc import Callable, Coroutine +from typing import TYPE_CHECKING, Any, TypeVar, Union if TYPE_CHECKING: from .cog import Cog diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index d348b6c536..0b050a7b8f 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -29,7 +29,8 @@ import collections.abc import sys import traceback -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Iterable, TypeVar +from collections.abc import Callable, Coroutine, Iterable +from typing import TYPE_CHECKING, Any, TypeVar import discord diff --git a/discord/ext/commands/cog.py b/discord/ext/commands/cog.py index 871d3e816e..ac93818475 100644 --- a/discord/ext/commands/cog.py +++ b/discord/ext/commands/cog.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Generator, TypeVar +from collections.abc import Callable, Generator +from typing import TYPE_CHECKING, Any, TypeVar import discord diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index ca5a109f3f..25124379e0 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -27,12 +27,11 @@ import inspect import re +from collections.abc import Iterable from typing import ( TYPE_CHECKING, Any, Generic, - Iterable, - List, Literal, Protocol, TypeVar, @@ -1023,7 +1022,7 @@ def repl(match: re.Match) -> str: return discord.utils.escape_mentions(result) -class Greedy(List[T]): +class Greedy(list[T]): r"""A special converter that greedily consumes arguments until it can't. As a consequence of this behaviour, most input errors are silently discarded, since it is used as an indicator of when to stop parsing. @@ -1100,7 +1099,7 @@ def get_converter(param: inspect.Parameter) -> Any: return converter -_GenericAlias = type(List[T]) +_GenericAlias = type(list[T]) def is_generic_type(tp: Any, *, _GenericAlias: type = _GenericAlias) -> bool: diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index 6e58d37f7a..c2fe07b702 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -28,10 +28,12 @@ import asyncio import time from collections import deque -from typing import TYPE_CHECKING, Any, Callable, Deque, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Deque, TypeVar import discord.abc from discord.enums import Enum +from discord.utils import _get_event_loop from ...abc import PrivateChannel from .errors import MaxConcurrencyReached @@ -308,7 +310,7 @@ class _Semaphore: def __init__(self, number: int) -> None: self.value: int = number - self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.loop: asyncio.AbstractEventLoop = _get_event_loop() self._waiters: Deque[asyncio.Future] = deque() def __repr__(self) -> str: diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index 4a58ecfca3..33fd62f0c0 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -30,11 +30,10 @@ import functools import inspect import types +from collections.abc import Callable, Generator from typing import ( TYPE_CHECKING, Any, - Callable, - Generator, Generic, Literal, TypeVar, @@ -67,7 +66,9 @@ from .errors import * if TYPE_CHECKING: - from typing_extensions import Concatenate, ParamSpec, TypeGuard + from typing import Concatenate, TypeGuard + + from typing_extensions import ParamSpec from discord.message import Message diff --git a/discord/ext/commands/errors.py b/discord/ext/commands/errors.py index c768fd7c00..8263af7150 100644 --- a/discord/ext/commands/errors.py +++ b/discord/ext/commands/errors.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Callable +from typing import TYPE_CHECKING, Any from discord.errors import ClientException, DiscordException diff --git a/discord/ext/commands/flags.py b/discord/ext/commands/flags.py index ec218b891f..0c2b925b74 100644 --- a/discord/ext/commands/flags.py +++ b/discord/ext/commands/flags.py @@ -28,8 +28,10 @@ import inspect import re import sys +from collections.abc import Iterator from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Iterator, Literal, Pattern, TypeVar, Union +from re import Pattern +from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union from discord.utils import ( MISSING, diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index ab40be08ec..0d9218b581 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -25,7 +25,6 @@ from __future__ import annotations import contextlib -from typing import List import discord from discord.errors import DiscordException @@ -935,7 +934,7 @@ def get_page_content( return Page(content=None, embeds=[], files=[page]) elif isinstance(page, discord.ui.View): return Page(content=None, embeds=[], files=[], custom_view=page) - elif isinstance(page, List): + elif isinstance(page, list): if all(isinstance(x, discord.Embed) for x in page): return Page(content=None, embeds=page, files=[]) if all(isinstance(x, discord.File) for x in page): diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 9bdde87f23..c1d1e72274 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -31,14 +31,14 @@ import inspect import sys import traceback -from collections.abc import Sequence -from typing import Any, Awaitable, Callable, Generic, TypeVar, cast +from collections.abc import Awaitable, Callable, Sequence +from typing import Any, Generic, TypeVar, cast import aiohttp import discord from discord.backoff import ExponentialBackoff -from discord.utils import MISSING +from discord.utils import MISSING, _get_event_loop __all__ = ("loop",) @@ -384,7 +384,7 @@ def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]: args = (self._injected, *args) if self.loop is MISSING: - self.loop = asyncio.get_event_loop() + self.loop = _get_event_loop() self._task = self.loop.create_task(self._loop(*args, **kwargs)) return self._task @@ -825,8 +825,8 @@ def loop( using an exponential back-off algorithm similar to the one used in :meth:`discord.Client.connect`. loop: :class:`asyncio.AbstractEventLoop` - The loop to use to register the task, if not given - defaults to :func:`asyncio.get_event_loop`. + The loop to use to register the task, if not given the default event loop is used via + :func:`asyncio.get_event_loop()` if it exists or one is created via :func:`asyncio.new_event_loop()`. overlap: Union[:class:`bool`, :class:`int`] Controls whether overlapping executions of the task loop are allowed. Set to False (default) to run iterations one at a time, True for unlimited overlap, or an int to cap the number of concurrent runs. diff --git a/discord/flags.py b/discord/flags.py index 53c1ccd98b..875276df35 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import Any, Callable, ClassVar, Iterator, TypeVar, overload +from collections.abc import Callable, Iterator +from typing import Any, ClassVar, TypeVar, overload from .enums import UserFlags diff --git a/discord/guild.py b/discord/guild.py index 63ba4aee5a..f995dd3222 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -28,15 +28,13 @@ import copy import datetime import unicodedata +from collections.abc import Sequence from typing import ( TYPE_CHECKING, Any, ClassVar, - List, NamedTuple, Optional, - Sequence, - Tuple, TypeVar, Union, overload, @@ -133,7 +131,7 @@ GuildChannel = Union[ VoiceChannel, StageChannel, TextChannel, ForumChannel, CategoryChannel ] - ByCategoryItem = Tuple[Optional[CategoryChannel], List[GuildChannel]] + ByCategoryItem = tuple[Optional[CategoryChannel], list[GuildChannel]] T = TypeVar("T") diff --git a/discord/http.py b/discord/http.py index 0717feadf5..40ebcd7c1f 100644 --- a/discord/http.py +++ b/discord/http.py @@ -29,13 +29,10 @@ import logging import sys import weakref +from collections.abc import AsyncGenerator, Coroutine, Iterable, Sequence from typing import ( TYPE_CHECKING, Any, - AsyncGenerator, - Coroutine, - Iterable, - Sequence, TypeVar, ) from urllib.parse import quote as _uriquote @@ -55,7 +52,7 @@ from .file import VoiceMessage from .gateway import DiscordClientWebSocketResponse from .soundboard import PartialSoundboardSound, SoundboardSound -from .utils import MISSING +from .utils import MISSING, _get_event_loop _log = logging.getLogger(__name__) @@ -192,7 +189,7 @@ def __init__( unsync_clock: bool = True, ) -> None: self.loop: asyncio.AbstractEventLoop = ( - asyncio.get_event_loop() if loop is None else loop + _get_event_loop() if loop is None else loop ) self.connector = connector self.__session: aiohttp.ClientSession = MISSING # filled in static_login diff --git a/discord/interactions.py b/discord/interactions.py index b325e4c20f..a77ab8f9f8 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -27,7 +27,8 @@ import asyncio import datetime -from typing import TYPE_CHECKING, Any, Coroutine, Union, overload +from collections.abc import Coroutine +from typing import TYPE_CHECKING, Any, Union, overload from typing_extensions import deprecated diff --git a/discord/iterators.py b/discord/iterators.py index b074aefdc4..c436f642f7 100644 --- a/discord/iterators.py +++ b/discord/iterators.py @@ -27,14 +27,10 @@ import asyncio import datetime +from collections.abc import AsyncIterator, Awaitable, Callable, Generator from typing import ( TYPE_CHECKING, Any, - AsyncIterator, - Awaitable, - Callable, - Generator, - List, TypeVar, Union, ) @@ -137,7 +133,7 @@ def _identity(x): return x -class _ChunkedAsyncIterator(_AsyncIterator[List[T]]): +class _ChunkedAsyncIterator(_AsyncIterator[list[T]]): def __init__(self, iterator, max_size): self.iterator = iterator self.max_size = max_size diff --git a/discord/message.py b/discord/message.py index 736397ee4e..0d4703d4c5 100644 --- a/discord/message.py +++ b/discord/message.py @@ -28,14 +28,12 @@ import datetime import io import re +from collections.abc import AsyncGenerator, Callable, Sequence from os import PathLike from typing import ( TYPE_CHECKING, Any, - AsyncGenerator, - Callable, ClassVar, - Sequence, TypeVar, Union, overload, diff --git a/discord/oggparse.py b/discord/oggparse.py index 4901c38b77..70dc7f2e25 100644 --- a/discord/oggparse.py +++ b/discord/oggparse.py @@ -26,7 +26,8 @@ from __future__ import annotations import struct -from typing import IO, TYPE_CHECKING, ClassVar, Generator +from collections.abc import Generator +from typing import IO, TYPE_CHECKING, ClassVar from .errors import DiscordException diff --git a/discord/opus.py b/discord/opus.py index f6c42fdd04..35f3e665aa 100644 --- a/discord/opus.py +++ b/discord/opus.py @@ -33,7 +33,8 @@ import os.path import struct import sys -from typing import TYPE_CHECKING, Any, Callable, Literal, TypedDict, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar try: import davey diff --git a/discord/permissions.py b/discord/permissions.py index fad9cdf1f7..17f77fede0 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterator, TypeVar +from collections.abc import Callable, Iterator +from typing import TYPE_CHECKING, Any, ClassVar, TypeVar from .flags import BaseFlags, alias_flag_value, fill_with_flags, flag_value diff --git a/discord/player.py b/discord/player.py index f80bba9c1f..007bd7e8d7 100644 --- a/discord/player.py +++ b/discord/player.py @@ -37,8 +37,9 @@ import threading import time import warnings +from collections.abc import Callable from math import floor -from typing import IO, TYPE_CHECKING, Any, Callable, Generic, TypeVar +from typing import IO, TYPE_CHECKING, Any, Generic, TypeVar from .enums import SpeakingState from .errors import ClientException diff --git a/discord/shard.py b/discord/shard.py index fd6a08a047..6d5dfc4dbf 100644 --- a/discord/shard.py +++ b/discord/shard.py @@ -27,7 +27,8 @@ import asyncio import logging -from typing import TYPE_CHECKING, Any, Callable, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, TypeVar import aiohttp diff --git a/discord/soundboard.py b/discord/soundboard.py index 6fae037090..d320cc2ab3 100644 --- a/discord/soundboard.py +++ b/discord/soundboard.py @@ -24,7 +24,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Coroutine +from collections.abc import Coroutine +from typing import TYPE_CHECKING, Any from typing_extensions import override diff --git a/discord/state.py b/discord/state.py index 574c973c52..759fa26a66 100644 --- a/discord/state.py +++ b/discord/state.py @@ -32,13 +32,11 @@ import logging import os from collections import OrderedDict, deque +from collections.abc import Callable, Coroutine, Sequence from typing import ( TYPE_CHECKING, Any, - Callable, - Coroutine, Deque, - Sequence, TypeVar, Union, ) diff --git a/discord/threads.py b/discord/threads.py index 8d7781770a..2a9a4bb6d5 100644 --- a/discord/threads.py +++ b/discord/threads.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Iterable +from collections.abc import Callable, Iterable +from typing import TYPE_CHECKING from .abc import Messageable, _purge_messages_helper from .enums import ( diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 3f45e35c19..da1c345f64 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, Literal, Union +from typing import TYPE_CHECKING, Literal, Union from ..permissions import Permissions from .channel import ChannelType @@ -272,7 +272,7 @@ class EditApplicationCommand(TypedDict): ApplicationIntegrationType = Literal[0, 1] _StringApplicationIntegrationType = Literal["0", "1"] -AuthorizingIntegrationOwners = Dict[_StringApplicationIntegrationType, Snowflake] +AuthorizingIntegrationOwners = dict[_StringApplicationIntegrationType, Snowflake] class InteractionCallbackResponse(TypedDict): diff --git a/discord/types/snowflake.py b/discord/types/snowflake.py index 1d28831570..3b89d29323 100644 --- a/discord/types/snowflake.py +++ b/discord/types/snowflake.py @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. """ -from typing import List, Union +from typing import Union Snowflake = Union[str, int] -SnowflakeList = List[Snowflake] +SnowflakeList = list[Snowflake] diff --git a/discord/ui/action_row.py b/discord/ui/action_row.py index a947f33f1e..730a1b2905 100644 --- a/discord/ui/action_row.py +++ b/discord/ui/action_row.py @@ -24,9 +24,9 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Iterator, Sequence from functools import partial -from typing import TYPE_CHECKING, ClassVar, Iterator, Literal, TypeVar, overload +from typing import TYPE_CHECKING, ClassVar, Literal, TypeVar, overload from ..components import ActionRow as ActionRowComponent from ..components import SelectDefaultValue, SelectOption, _component_factory diff --git a/discord/ui/button.py b/discord/ui/button.py index 7ba2a3a34e..a8ad878c38 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -27,7 +27,8 @@ import inspect import os -from typing import TYPE_CHECKING, Callable, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, TypeVar from ..components import Button as ButtonComponent from ..enums import ButtonStyle, ComponentType diff --git a/discord/ui/container.py b/discord/ui/container.py index 0ebd72a2b4..480e749f34 100644 --- a/discord/ui/container.py +++ b/discord/ui/container.py @@ -24,7 +24,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterator, TypeVar +from collections.abc import Iterator +from typing import TYPE_CHECKING, TypeVar from ..colour import Colour from ..components import Container as ContainerComponent diff --git a/discord/ui/core.py b/discord/ui/core.py index 21bb16871a..c98a2265aa 100644 --- a/discord/ui/core.py +++ b/discord/ui/core.py @@ -26,8 +26,9 @@ import asyncio import time +from collections.abc import Callable from itertools import groupby -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from ..utils import find, get from .item import Item, ItemCallbackType diff --git a/discord/ui/item.py b/discord/ui/item.py index 85132558bd..6236bb77fd 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -25,7 +25,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Generic, TypeVar +from collections.abc import Callable, Coroutine +from typing import TYPE_CHECKING, Any, Generic, TypeVar from ..interactions import Interaction diff --git a/discord/ui/label.py b/discord/ui/label.py index 69c519f0cd..720b02b226 100644 --- a/discord/ui/label.py +++ b/discord/ui/label.py @@ -24,8 +24,8 @@ from __future__ import annotations -from collections.abc import Sequence -from typing import TYPE_CHECKING, Iterator, Literal, TypeVar, overload +from collections.abc import Iterator, Sequence +from typing import TYPE_CHECKING, Literal, TypeVar, overload from ..components import ( CheckboxGroupOption, diff --git a/discord/ui/modal.py b/discord/ui/modal.py index 6bb3a21d6a..8cf806ac12 100644 --- a/discord/ui/modal.py +++ b/discord/ui/modal.py @@ -28,12 +28,13 @@ import os import sys import time +from collections.abc import Iterator from functools import partial from itertools import groupby -from typing import TYPE_CHECKING, Any, Iterator, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from ..enums import ComponentType -from ..utils import find +from ..utils import _get_event_loop, find from .core import ItemInterface from .input_text import InputText from .item import ModalItem @@ -91,7 +92,7 @@ def __init__( for item in children: self.add_item(item) self._title = title - self.loop = asyncio.get_event_loop() + self.loop = _get_event_loop() def __repr__(self) -> str: attrs = " ".join( diff --git a/discord/ui/section.py b/discord/ui/section.py index 9b733999b7..f0a5f95ac0 100644 --- a/discord/ui/section.py +++ b/discord/ui/section.py @@ -24,8 +24,9 @@ from __future__ import annotations +from collections.abc import Iterator from functools import partial -from typing import TYPE_CHECKING, ClassVar, Iterator, TypeVar +from typing import TYPE_CHECKING, ClassVar, TypeVar from ..components import Section as SectionComponent from ..components import _component_factory diff --git a/discord/ui/select.py b/discord/ui/select.py index d6f5a105ca..df18c9385c 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -27,9 +27,9 @@ import inspect import os -from collections.abc import Sequence +from collections.abc import Callable, Sequence from functools import partial -from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, overload +from typing import TYPE_CHECKING, Any, Generic, Literal, overload from typing_extensions import Self, TypeVar diff --git a/discord/ui/view.py b/discord/ui/view.py index 281df6fb47..fece3b03cc 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -30,14 +30,13 @@ import os import sys import time +from collections.abc import Iterator, Sequence from functools import partial from itertools import groupby from typing import ( TYPE_CHECKING, Any, ClassVar, - Iterator, - Sequence, TypeVar, ) diff --git a/discord/utils.py b/discord/utils.py index cc6d9d3b19..126d91c146 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -42,24 +42,26 @@ import warnings from base64 import b64encode from bisect import bisect_left +from collections.abc import ( + AsyncIterator, + Awaitable, + Callable, + Coroutine, + Iterable, + Iterator, + Mapping, + Sequence, +) from inspect import isawaitable as _isawaitable from inspect import signature as _signature from operator import attrgetter from typing import ( TYPE_CHECKING, Any, - AsyncIterator, - Awaitable, - Callable, - Coroutine, ForwardRef, Generic, - Iterable, - Iterator, Literal, - Mapping, Protocol, - Sequence, TypeVar, Union, overload, @@ -1665,3 +1667,21 @@ def warn_if_voice_dependencies_missing() -> None: deps, "is" if len(missing) == 1 else "are", ) + + +def _get_event_loop() -> asyncio.AbstractEventLoop: + """Get the current event loop, creating one if necessary. + + Returns + ------- + asyncio.AbstractEventLoop + The current event loop. + """ + if sys.version_info >= (3, 14): + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop + return asyncio.get_event_loop() diff --git a/discord/voice/packets/core.py b/discord/voice/packets/core.py index c38fc9face..8fd82c232b 100644 --- a/discord/voice/packets/core.py +++ b/discord/voice/packets/core.py @@ -28,7 +28,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from typing_extensions import Final + from typing import Final OPUS_SILENCE: Final = b"\xf8\xff\xfe" diff --git a/discord/voice/packets/rtp.py b/discord/voice/packets/rtp.py index dd9520cf4e..cbee2e1c96 100644 --- a/discord/voice/packets/rtp.py +++ b/discord/voice/packets/rtp.py @@ -32,7 +32,7 @@ from .core import OPUS_SILENCE, Packet if TYPE_CHECKING: - from typing_extensions import Final + from typing import Final MAX_UINT_32 = 0xFFFFFFFF MAX_UINT_16 = 0xFFFF diff --git a/examples/app_commands/slash_options.py b/examples/app_commands/slash_options.py index 4a9eda93cc..be625738b1 100644 --- a/examples/app_commands/slash_options.py +++ b/examples/app_commands/slash_options.py @@ -47,7 +47,7 @@ async def hello( ) async def select_channel( ctx: discord.ApplicationContext, - channel: Union[discord.TextChannel, discord.VoiceChannel], + channel: discord.TextChannel | discord.VoiceChannel, ): await ctx.respond(f"Hi! You selected {channel.mention} channel.") diff --git a/examples/converters.py b/examples/converters.py index bbf0fb9e51..1d2359e944 100644 --- a/examples/converters.py +++ b/examples/converters.py @@ -1,7 +1,6 @@ # This example requires the 'members' privileged intent to use the Member converter, # and the 'message_content' privileged intent for prefixed commands. -from typing import Union import discord from discord.ext import commands @@ -90,9 +89,7 @@ async def notify(ctx: commands.Context, target: ChannelOrMemberConverter): @bot.command() -async def ignore( - ctx: commands.Context, target: Union[discord.Member, discord.TextChannel] -): +async def ignore(ctx: commands.Context, target: discord.Member | discord.TextChannel): # This command signature utilises the `typing.Union` typehint. # The `commands` framework attempts a conversion of each type in this Union *in order*. # So, it will attempt to convert whatever is passed to `target` to a `discord.Member` instance. diff --git a/examples/secret.py b/examples/secret.py index 7b0b77d06c..b78ce2e88d 100644 --- a/examples/secret.py +++ b/examples/secret.py @@ -1,7 +1,6 @@ # This example requires the 'members' privileged intent to use the Member converter, # and the 'message_content' privileged intent for prefixed commands. -from typing import Union import discord from discord.ext import commands @@ -25,9 +24,7 @@ async def secret(ctx: commands.Context): await ctx.send("Shh!", delete_after=5) -def create_overwrites( - ctx: commands.Context, *objects: Union[discord.Role, discord.Member] -): +def create_overwrites(ctx: commands.Context, *objects: discord.Role | discord.Member): """ This is just a helper function that creates the overwrites for the voice/text channels. @@ -65,7 +62,7 @@ def create_overwrites( async def text( ctx: commands.Context, name: str, - *objects: Union[discord.Role, discord.Member], + *objects: discord.Role | discord.Member, ): """ This makes a text channel with the passed name that @@ -90,7 +87,7 @@ async def text( async def voice( ctx: commands.Context, name: str, - *objects: Union[discord.Role, discord.Member], + *objects: discord.Role | discord.Member, ): """ This does the same thing as the `text` subcommand diff --git a/examples/views/tic_tac_toe.py b/examples/views/tic_tac_toe.py index 1d240c2356..19e5415bc9 100644 --- a/examples/views/tic_tac_toe.py +++ b/examples/views/tic_tac_toe.py @@ -1,6 +1,5 @@ # This example requires the 'message_content' privileged intent for prefixed commands. -from typing import List import discord from discord.ext import commands @@ -63,7 +62,7 @@ async def callback(self, interaction: discord.Interaction): class TicTacToe(discord.ui.View): # This tells the IDE or linter that all our children will be TicTacToeButtons. # This is not required. - children: List[TicTacToeButton] + children: list[TicTacToeButton] X = -1 O = 1 Tie = 2 diff --git a/pyproject.toml b/pyproject.toml index c12d562b09..ab7e251bea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ ] description = "A Python wrapper for the Discord API" readme = { content-type = "text/x-rst", file = "README.rst" } -requires-python = ">=3.10, <3.14" +requires-python = ">=3.10, <3.15" license = "MIT" license-files = ["LICENSE"] classifiers = [ @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Internet", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", @@ -84,7 +85,7 @@ test = [ [tool.setuptools_scm] [tool.black] -target-version = ['py310', 'py311', 'py312', 'py313'] +target-version = ['py310', 'py311', 'py312', 'py313', 'py314'] [tool.isort] profile = "black" @@ -125,7 +126,7 @@ asyncio_default_fixture_loop_scope = "function" [tool.tox] requires = ["tox>=4"] -env_list = ["3.13", "3.12", "3.11", "3.10", "3.13-novoice"] +env_list = ["3.14", "3.13", "3.12", "3.11", "3.10", "3.14-novoice"] [tool.tox.env_run_base] description = "run unit tests" @@ -133,7 +134,7 @@ commands = [["pytest", { replace = "posargs", default = ["tests"], extend = true dependency_groups = ["test"] extras = ["voice"] -[tool.tox.env."3.13-novoice"] +[tool.tox.env."3.14-novoice"] description = "run import and warning tests without the voice extra" commands = [[ "pytest", @@ -144,7 +145,8 @@ extras = [] # GitHub actions [tool.tox.gh.python] -"3.13" = ["3.13", "3.13-novoice"] +"3.14" = ["3.14", "3.14-novoice"] +"3.13" = ["3.13"] "3.12" = ["3.12"] "3.11" = ["3.11"] "3.10" = ["3.10"]