From 57f1fa3a107cdf1dd1ccb0eec32e4f42e25cd25b Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 11:39:20 +0300 Subject: [PATCH 1/9] refactor: drop OrderedDict in favor of dict --- discord/commands/core.py | 9 +++------ discord/state.py | 9 +++++---- docs/extensions/attributetable.py | 12 +++++------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index e2354e64ad..eefa2777e4 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -31,7 +31,6 @@ import inspect import re import types -from collections import OrderedDict from collections.abc import Callable, Coroutine, Generator from enum import Enum from typing import ( @@ -479,7 +478,7 @@ async def dispatch_error(self, ctx: ApplicationContext, error: Exception) -> Non ctx.bot.dispatch("application_command_error", ctx, error) def _get_signature_parameters(self): - return OrderedDict(inspect.signature(self.callback).parameters) + return dict(inspect.signature(self.callback).parameters) def error(self, coro): """A decorator that registers a coroutine as a local error handler. @@ -915,7 +914,7 @@ def _match_option_param_names(self, params, options): ) o._parameter_name = p_name - left_out_params = OrderedDict() + left_out_params = {} for k, v in params: left_out_params[k] = v options.extend(self._parse_options(left_out_params, check_params=False)) @@ -925,9 +924,7 @@ def _match_option_param_names(self, params, options): def _is_typing_union(self, annotation): return getattr(annotation, "__origin__", None) is Union or type( annotation - ) is getattr( - types, "UnionType", Union - ) # type: ignore + ) is getattr(types, "UnionType", Union) # type: ignore def _is_typing_optional(self, annotation): return self._is_typing_union(annotation) and type(None) in annotation.__args__ # type: ignore diff --git a/discord/state.py b/discord/state.py index 0662fc64d2..74203e6f81 100644 --- a/discord/state.py +++ b/discord/state.py @@ -31,7 +31,7 @@ import itertools import logging import os -from collections import OrderedDict, deque +from collections import deque from collections.abc import Callable, Coroutine, Sequence from typing import ( TYPE_CHECKING, @@ -290,7 +290,7 @@ def clear(self, *, views: bool = True) -> None: self._sounds: dict[int, SoundboardSound] = {} # LRU of max size 128 - self._private_channels: OrderedDict[int, PrivateChannel] = OrderedDict() + self._private_channels: dict[int, PrivateChannel] = {} # extra dict to look up private channels by user id self._private_channels_by_user: dict[int, DMChannel] = {} if self.max_messages is not None: @@ -497,7 +497,7 @@ def _get_private_channel(self, channel_id: int | None) -> PrivateChannel | None: except KeyError: return None else: - self._private_channels.move_to_end(channel_id) # type: ignore + self._private_channels[channel_id] = self._private_channels.pop(channel_id) # type: ignore return value def _get_private_channel_by_user(self, user_id: int | None) -> DMChannel | None: @@ -509,7 +509,8 @@ def _add_private_channel(self, channel: PrivateChannel) -> None: self._private_channels[channel_id] = channel if len(self._private_channels) > 128: - _, to_remove = self._private_channels.popitem(last=False) + oldest_key = next(iter(self._private_channels)) + to_remove = self._private_channels.pop(oldest_key) if isinstance(to_remove, DMChannel) and to_remove.recipient: self._private_channels_by_user.pop(to_remove.recipient.id, None) diff --git a/docs/extensions/attributetable.py b/docs/extensions/attributetable.py index e8c469efb4..560fc949a0 100644 --- a/docs/extensions/attributetable.py +++ b/docs/extensions/attributetable.py @@ -1,7 +1,7 @@ import importlib import inspect import re -from collections import OrderedDict, namedtuple +from collections import namedtuple from docutils import nodes from sphinx import addnodes @@ -200,12 +200,10 @@ def get_class_results(lookup, modulename, name, fullname): module = importlib.import_module(modulename) cls = getattr(module, name) - groups = OrderedDict( - [ - (_("Attributes"), []), - (_("Methods"), []), - ] - ) + groups = { + _("Attributes"): [], + _("Methods"): [], + } try: members = lookup[fullname] From 44cc4f20641b912fc1a2a6014ec03abf31e56110 Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 11:40:53 +0300 Subject: [PATCH 2/9] refactor: drop obsolete PY_310 check --- discord/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index 8a8c4439b0..ca6ed924d6 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -1321,8 +1321,6 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[list[T]]: return _chunk(iterator, max_size) -PY_310 = sys.version_info >= (3, 10) - def flatten_literal_params(parameters: Iterable[Any]) -> tuple[Any, ...]: params = [] @@ -1365,7 +1363,7 @@ def evaluate_annotation( is_literal = False args = tp.__args__ if not hasattr(tp, "__origin__"): - if PY_310 and tp.__class__ is types.UnionType: # type: ignore + if tp.__class__ is types.UnionType: # type: ignore converted = Union[args] # type: ignore return evaluate_annotation(converted, globals, locals, cache) @@ -1377,8 +1375,6 @@ def evaluate_annotation( except ValueError: pass if tp.__origin__ is Literal: - if not PY_310: - args = flatten_literal_params(tp.__args__) implicit_str = False is_literal = True From acfd8be36a47a79ccff4b39669e8878a1a174435 Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 11:50:37 +0300 Subject: [PATCH 3/9] refactor: replace if-elif chain with match-case --- discord/enums.py | 86 ++++++++++++++++++++++++------------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 5011a338c2..245eadd6c3 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -536,49 +536,49 @@ def category(self) -> AuditLogActionCategory | None: @property def target_type(self) -> str | None: - v = self.value - if v == -1: - return "all" - elif v < 10: - return "guild" - elif v < 20: - return "channel" - elif v < 30: - return "user" - elif v < 40: - return "role" - elif v < 50: - return "invite" - elif v < 60: - return "webhook" - elif v < 70: - return "emoji" - elif v == 73: - return "channel" - elif v < 80: - return "message" - elif v < 83: - return "integration" - elif v < 90: - return "stage_instance" - elif v < 93: - return "sticker" - elif v < 103: - return "scheduled_event" - elif v < 113: - return "thread" - elif v < 122: - return "application_command_permission" - elif v < 146: - return "auto_moderation_rule" - elif v < 152: - return "monetization" - elif v < 168: - return "onboarding" - elif v < 192: - return "server_guide" - elif v < 194: - return "voice_channel_status" + match self.value: + case -1: + return "all" + case v if v < 10: + return "guild" + case v if v < 20: + return "channel" + case v if v < 30: + return "user" + case v if v < 40: + return "role" + case v if v < 50: + return "invite" + case v if v < 60: + return "webhook" + case v if v < 70: + return "emoji" + case 73: + return "channel" + case v if v < 80: + return "message" + case v if v < 83: + return "integration" + case v if v < 90: + return "stage_instance" + case v if v < 93: + return "sticker" + case v if v < 103: + return "scheduled_event" + case v if v < 113: + return "thread" + case v if v < 122: + return "application_command_permission" + case v if v < 146: + return "auto_moderation_rule" + case v if v < 152: + return "monetization" + case v if v < 168: + return "onboarding" + case v if v < 192: + return "server_guide" + case v if v < 194: + return "voice_channel_status" class UserFlags(Enum): From 67939904c01cb1399712bd49443dcf6f2b4bad74 Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 11:56:31 +0300 Subject: [PATCH 4/9] refactor: remove obsolete UnionType hasattr guard --- discord/enums.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index 245eadd6c3..6a27be6727 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -840,11 +840,7 @@ def from_datatype(cls, datatype): else: raise TypeError("Invalid usage of typing.Union") - py_3_10_union_type = hasattr(types, "UnionType") and isinstance( - datatype, types.UnionType - ) - - if py_3_10_union_type or getattr(datatype, "__origin__", None) is Union: + if isinstance(datatype, types.UnionType) or getattr(datatype, "__origin__", None) is Union: # Python 3.10+ "|" operator or typing.Union has been used. The __args__ attribute is a tuple of the types. # Type checking fails for this case, so ignore it. return cls.from_datatype(datatype.__args__) # type: ignore From a817dc4838a52e7d4da97e7b82be80cb9f2aa5bc Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 12:00:32 +0300 Subject: [PATCH 5/9] refactor: replace typing.Deque with collections.deque --- discord/ext/commands/cooldowns.py | 10 +++++++--- discord/state.py | 7 +++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/discord/ext/commands/cooldowns.py b/discord/ext/commands/cooldowns.py index c2fe07b702..2d345d3b88 100644 --- a/discord/ext/commands/cooldowns.py +++ b/discord/ext/commands/cooldowns.py @@ -29,7 +29,7 @@ import time from collections import deque from collections.abc import Callable -from typing import TYPE_CHECKING, Any, Deque, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar import discord.abc from discord.enums import Enum @@ -83,7 +83,11 @@ def get_key(self, msg: Message) -> Any: # and that yields the same result as for a guild with only the @everyone role # NOTE: PrivateChannel doesn't actually have an id attribute, but we assume we are # receiving a DMChannel or GroupChannel which inherit from PrivateChannel and do - return (msg.channel if isinstance(msg.channel, PrivateChannel) else msg.author.top_role).id # type: ignore + return ( + msg.channel + if isinstance(msg.channel, PrivateChannel) + else msg.author.top_role + ).id # type: ignore def __call__(self, msg: Message) -> Any: return self.get_key(msg) @@ -311,7 +315,7 @@ class _Semaphore: def __init__(self, number: int) -> None: self.value: int = number self.loop: asyncio.AbstractEventLoop = _get_event_loop() - self._waiters: Deque[asyncio.Future] = deque() + self._waiters: deque[asyncio.Future] = deque() def __repr__(self) -> str: return f"<_Semaphore value={self.value} waiters={len(self._waiters)}>" diff --git a/discord/state.py b/discord/state.py index 74203e6f81..5002a6d045 100644 --- a/discord/state.py +++ b/discord/state.py @@ -36,7 +36,6 @@ from typing import ( TYPE_CHECKING, Any, - Deque, TypeVar, Union, ) @@ -294,9 +293,9 @@ def clear(self, *, views: bool = True) -> None: # extra dict to look up private channels by user id self._private_channels_by_user: dict[int, DMChannel] = {} if self.max_messages is not None: - self._messages: Deque[Message] | None = deque(maxlen=self.max_messages) + self._messages: deque[Message] | None = deque(maxlen=self.max_messages) else: - self._messages: Deque[Message] | None = None + self._messages: deque[Message] | None = None def process_chunk_requests( self, guild_id: int, nonce: str | None, members: list[Member], complete: bool @@ -1515,7 +1514,7 @@ def parse_guild_delete(self, data) -> None: # do a cleanup of the messages cache if self._messages is not None: - self._messages: Deque[Message] | None = deque( + self._messages: deque[Message] | None = deque( (msg for msg in self._messages if msg.guild != guild), maxlen=self.max_messages, ) From 9e4d15294bd9c63178d729fcf60b11beee7fc561 Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 12:09:46 +0300 Subject: [PATCH 6/9] refactor: replace typing_extensions.ParamSpec with typing.ParamSpec --- discord/commands/context.py | 3 +- discord/commands/core.py | 4 +-- discord/ext/commands/_types.py | 2 ++ discord/ext/commands/context.py | 8 +++-- discord/ext/commands/core.py | 16 ++++----- discord/ui/select.py | 4 +-- discord/utils.py | 57 ++++++++++++++++----------------- discord/voice/_types.py | 2 +- discord/voice/client.py | 2 +- 9 files changed, 51 insertions(+), 47 deletions(-) diff --git a/discord/commands/context.py b/discord/commands/context.py index b7206d0946..e1bc5a808a 100644 --- a/discord/commands/context.py +++ b/discord/commands/context.py @@ -33,8 +33,7 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable - - from typing_extensions import ParamSpec + from typing import ParamSpec import discord diff --git a/discord/commands/core.py b/discord/commands/core.py index eefa2777e4..587f7901bc 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -83,9 +83,9 @@ ) if TYPE_CHECKING: - from typing import Concatenate + from typing import Concatenate, ParamSpec - from typing_extensions import Never, ParamSpec + from typing_extensions import Never from .. import Permissions from ..bot import C diff --git a/discord/ext/commands/_types.py b/discord/ext/commands/_types.py index 643dfd7ab0..99d9a5bc95 100644 --- a/discord/ext/commands/_types.py +++ b/discord/ext/commands/_types.py @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. """ +from __future__ import annotations + from collections.abc import Callable, Coroutine from typing import TYPE_CHECKING, Any, TypeVar, Union diff --git a/discord/ext/commands/context.py b/discord/ext/commands/context.py index 11e2216fce..780db189de 100644 --- a/discord/ext/commands/context.py +++ b/discord/ext/commands/context.py @@ -34,7 +34,7 @@ from discord.message import Message if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing import ParamSpec from discord.abc import MessageableChannel from discord.guild import Guild @@ -311,7 +311,11 @@ def me(self) -> Member | ClientUser: message contexts, or when :meth:`Intents.guilds` is absent. """ # bot.user will never be None at this point. - return self.guild.me if self.guild is not None and self.guild.me is not None else self.bot.user # type: ignore + return ( + self.guild.me + if self.guild is not None and self.guild.me is not None + else self.bot.user + ) # type: ignore @property def voice_client(self) -> VoiceProtocol | None: diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index d910a175d5..fefc44417f 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -66,9 +66,7 @@ from .errors import * if TYPE_CHECKING: - from typing import Concatenate, TypeGuard - - from typing_extensions import ParamSpec + from typing import Concatenate, ParamSpec, TypeGuard from discord.message import Message @@ -396,7 +394,9 @@ def __init__( # bandaid for the fact that sometimes parent can be the bot instance parent = kwargs.get("parent") - self.parent: GroupMixin | None = parent if isinstance(parent, _BaseCommand) else None # type: ignore + self.parent: GroupMixin | None = ( + parent if isinstance(parent, _BaseCommand) else None + ) # type: ignore self._before_invoke: Hook | None = None try: @@ -1082,9 +1082,7 @@ def _is_typing_optional(self, annotation: T | T | None) -> TypeGuard[T | None]: return ( getattr(annotation, "__origin__", None) is Union or type(annotation) is getattr(types, "UnionType", Union) - ) and type( - None - ) in annotation.__args__ # type: ignore + ) and type(None) in annotation.__args__ # type: ignore @property def signature(self) -> str: @@ -1203,7 +1201,9 @@ async def can_run(self, ctx: Context) -> bool: # since we have no checks, then we just return True. return True - return await discord.utils.async_all(predicate(ctx) for predicate in predicates) # type: ignore + return await discord.utils.async_all( + predicate(ctx) for predicate in predicates + ) # type: ignore finally: ctx.command = original diff --git a/discord/ui/select.py b/discord/ui/select.py index df18c9385c..b220d9cf60 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -29,9 +29,9 @@ import os from collections.abc import Callable, Sequence from functools import partial -from typing import TYPE_CHECKING, Any, Generic, Literal, overload +from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, overload -from typing_extensions import Self, TypeVar +from typing_extensions import Self from ..channel import _threaded_guild_channel_factory from ..components import SelectDefaultValue, SelectMenu, SelectOption diff --git a/discord/utils.py b/discord/utils.py index ca6ed924d6..c968e64f8a 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -71,19 +71,19 @@ if TYPE_CHECKING: from discord import ( + AppEmoji, + CategoryChannel, Client, - VoiceChannel, - TextChannel, ForumChannel, + Guild, + GuildEmoji, + Member, + Role, StageChannel, - CategoryChannel, + TextChannel, Thread, - Member, User, - Guild, - Role, - GuildEmoji, - AppEmoji, + VoiceChannel, ) from .errors import HTTPException, InvalidArgument, InvalidData @@ -96,32 +96,32 @@ HAS_MSGSPEC = True __all__ = ( - "parse_time", - "warn_deprecated", + "MISSING", + "as_chunks", + "basic_autocomplete", "deprecated", - "oauth_url", - "snowflake_time", - "time_snowflake", + "escape_markdown", + "escape_mentions", + "filter_params", "find", + "format_dt", + "generate_snowflake", "get", "get_or_fetch", - "sleep_until", - "utcnow", - "resolve_invite", - "resolve_template", - "remove_markdown", - "escape_markdown", - "escape_mentions", - "raw_mentions", + "oauth_url", + "parse_time", "raw_channel_mentions", + "raw_mentions", "raw_role_mentions", - "as_chunks", - "format_dt", - "generate_snowflake", - "basic_autocomplete", - "filter_params", - "MISSING", + "remove_markdown", + "resolve_invite", + "resolve_template", + "sleep_until", + "snowflake_time", + "time_snowflake", "users_to_csv", + "utcnow", + "warn_deprecated", ) _log = logging.getLogger(__name__) @@ -158,7 +158,7 @@ def __repr__(self) -> str: MISSING: Any = _MissingSentinel() if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing import ParamSpec from .abc import Snowflake from .commands.context import AutocompleteContext @@ -1321,7 +1321,6 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[list[T]]: return _chunk(iterator, max_size) - def flatten_literal_params(parameters: Iterable[Any]) -> tuple[Any, ...]: params = [] literal_cls = type(Literal[0]) diff --git a/discord/voice/_types.py b/discord/voice/_types.py index 1426e1d6e8..da36e22fae 100644 --- a/discord/voice/_types.py +++ b/discord/voice/_types.py @@ -28,7 +28,7 @@ from typing import TYPE_CHECKING, Generic, TypeVar if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing import ParamSpec from discord import abc from discord.client import Client diff --git a/discord/voice/client.py b/discord/voice/client.py index 01f59fbf32..f73f66e612 100644 --- a/discord/voice/client.py +++ b/discord/voice/client.py @@ -52,7 +52,7 @@ import nacl.utils if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing import ParamSpec from discord import abc from discord.client import Client From e3e2e08693b865590f1a4cb1ba907bee5775880b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:18:32 +0000 Subject: [PATCH 7/9] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/commands/core.py | 4 +++- discord/enums.py | 5 ++++- discord/ext/commands/core.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/discord/commands/core.py b/discord/commands/core.py index 587f7901bc..66ba4aa84f 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -924,7 +924,9 @@ def _match_option_param_names(self, params, options): def _is_typing_union(self, annotation): return getattr(annotation, "__origin__", None) is Union or type( annotation - ) is getattr(types, "UnionType", Union) # type: ignore + ) is getattr( + types, "UnionType", Union + ) # type: ignore def _is_typing_optional(self, annotation): return self._is_typing_union(annotation) and type(None) in annotation.__args__ # type: ignore diff --git a/discord/enums.py b/discord/enums.py index 6a27be6727..4b87030062 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -840,7 +840,10 @@ def from_datatype(cls, datatype): else: raise TypeError("Invalid usage of typing.Union") - if isinstance(datatype, types.UnionType) or getattr(datatype, "__origin__", None) is Union: + if ( + isinstance(datatype, types.UnionType) + or getattr(datatype, "__origin__", None) is Union + ): # Python 3.10+ "|" operator or typing.Union has been used. The __args__ attribute is a tuple of the types. # Type checking fails for this case, so ignore it. return cls.from_datatype(datatype.__args__) # type: ignore diff --git a/discord/ext/commands/core.py b/discord/ext/commands/core.py index fefc44417f..dad32f30a1 100644 --- a/discord/ext/commands/core.py +++ b/discord/ext/commands/core.py @@ -1082,7 +1082,9 @@ def _is_typing_optional(self, annotation: T | T | None) -> TypeGuard[T | None]: return ( getattr(annotation, "__origin__", None) is Union or type(annotation) is getattr(types, "UnionType", Union) - ) and type(None) in annotation.__args__ # type: ignore + ) and type( + None + ) in annotation.__args__ # type: ignore @property def signature(self) -> str: From d586f1881a5427a7ba0feb7e173a67b64c668c1d Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 12:25:24 +0300 Subject: [PATCH 8/9] revert: TypeVar, since it uses default param (3.13) --- discord/ui/select.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/ui/select.py b/discord/ui/select.py index b220d9cf60..df18c9385c 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -29,9 +29,9 @@ import os from collections.abc import Callable, Sequence from functools import partial -from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, overload +from typing import TYPE_CHECKING, Any, Generic, Literal, overload -from typing_extensions import Self +from typing_extensions import Self, TypeVar from ..channel import _threaded_guild_channel_factory from ..components import SelectDefaultValue, SelectMenu, SelectOption From fea27edab68507283fa1f5c9a067c12141117b1e Mon Sep 17 00:00:00 2001 From: vmphase Date: Thu, 30 Jul 2026 12:39:05 +0300 Subject: [PATCH 9/9] chore: remove unused flatten_literal_params and classproperty --- discord/utils.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/discord/utils.py b/discord/utils.py index c968e64f8a..44fc48a6eb 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -207,17 +207,6 @@ def __get__(self, instance: T | None, owner: type[T]) -> Any: return value -class classproperty(Generic[T_co]): - def __init__(self, fget: Callable[[Any], T_co]) -> None: - self.fget = fget - - def __get__(self, instance: Any | None, owner: type[Any]) -> T_co: - return self.fget(owner) - - def __set__(self, instance, value) -> None: - raise AttributeError("cannot set attribute") - - def cached_slot_property( name: str, ) -> Callable[[Callable[[T], T_co]], CachedSlotProperty[T, T_co]]: @@ -1321,17 +1310,6 @@ def as_chunks(iterator: _Iter[T], max_size: int) -> _Iter[list[T]]: return _chunk(iterator, max_size) -def flatten_literal_params(parameters: Iterable[Any]) -> tuple[Any, ...]: - params = [] - literal_cls = type(Literal[0]) - for p in parameters: - if isinstance(p, literal_cls): - params.extend(p.__args__) - else: - params.append(p) - return tuple(params) - - def normalise_optional_params(parameters: Iterable[Any]) -> tuple[Any, ...]: none_cls = type(None) return tuple(p for p in parameters if p is not none_cls) + (none_cls,)