diff --git a/docs/framework_commands.rst b/docs/framework_commands.rst index 8a7ae7716..6b6a1f861 100644 --- a/docs/framework_commands.rst +++ b/docs/framework_commands.rst @@ -40,11 +40,14 @@ extend functionalities used throughout the bot, as outlined below. .. automodule:: redbot.core.commands.converter :members: - :exclude-members: convert + :exclude-members: UserInputOptional, convert :no-undoc-members: .. autoclass:: APIToken + .. autodata:: UserInputOptional + :annotation: + ****************** Help Functionality ****************** diff --git a/redbot/cogs/audio/core/commands/player.py b/redbot/cogs/audio/core/commands/player.py index 89b67e325..4b986ed2a 100644 --- a/redbot/cogs/audio/core/commands/player.py +++ b/redbot/cogs/audio/core/commands/player.py @@ -10,6 +10,7 @@ from discord.embeds import EmptyEmbed from redbot.core.utils import AsyncIter from redbot.core import commands +from redbot.core.commands import UserInputOptional from redbot.core.utils.menus import DEFAULT_CONTROLS, close_menu, menu, next_page, prev_page from ...audio_dataclasses import _PARTIALLY_SUPPORTED_MUSIC_EXT, Query @@ -122,7 +123,7 @@ class PlayerCommands(MixinMeta, metaclass=CompositeMetaClass): @commands.guild_only() @commands.bot_has_permissions(embed_links=True) async def command_bumpplay( - self, ctx: commands.Context, play_now: Optional[bool] = False, *, query: str + self, ctx: commands.Context, play_now: UserInputOptional[bool] = False, *, query: str ): """Force play a URL or search for a track.""" query = Query.process_input(query, self.local_folder_current_path) diff --git a/redbot/cogs/audio/core/commands/playlists.py b/redbot/cogs/audio/core/commands/playlists.py index cb0ae37fa..fb742fa8a 100644 --- a/redbot/cogs/audio/core/commands/playlists.py +++ b/redbot/cogs/audio/core/commands/playlists.py @@ -12,6 +12,7 @@ import lavalink from redbot.core.utils import AsyncIter from redbot.core import commands +from redbot.core.commands import UserInputOptional from redbot.core.data_manager import cog_data_path from redbot.core.utils.chat_formatting import bold, pagify from redbot.core.utils.menus import DEFAULT_CONTROLS, menu @@ -636,7 +637,7 @@ class PlaylistCommands(MixinMeta, metaclass=CompositeMetaClass): self, ctx: commands.Context, playlist_matches: PlaylistConverter, - v2: Optional[bool] = False, + v2: UserInputOptional[bool] = False, *, scope_data: ScopeParser = None, ): diff --git a/redbot/cogs/image/image.py b/redbot/cogs/image/image.py index c5918b273..87aa23d49 100644 --- a/redbot/cogs/image/image.py +++ b/redbot/cogs/image/image.py @@ -5,6 +5,7 @@ import aiohttp from redbot.core.i18n import Translator, cog_i18n from redbot.core import checks, Config, commands +from redbot.core.commands import UserInputOptional _ = Translator("Image", __file__) @@ -47,7 +48,7 @@ class Image(commands.Cog): pass @_imgur.command(name="search") - async def imgur_search(self, ctx, count: Optional[int] = 1, *, term: str): + async def imgur_search(self, ctx, count: UserInputOptional[int] = 1, *, term: str): """Search Imgur for the specified term. Use `count` to choose how many images should be returned. diff --git a/redbot/cogs/mod/kickban.py b/redbot/cogs/mod/kickban.py index 53221478f..cfb7be308 100644 --- a/redbot/cogs/mod/kickban.py +++ b/redbot/cogs/mod/kickban.py @@ -6,6 +6,7 @@ from typing import Optional, Union import discord from redbot.core import commands, i18n, checks, modlog +from redbot.core.commands import UserInputOptional from redbot.core.utils import AsyncIter from redbot.core.utils.chat_formatting import pagify, humanize_number, bold, humanize_list from redbot.core.utils.mod import is_allowed_by_hierarchy, get_audit_reason @@ -427,7 +428,7 @@ class KickBanMixin(MixinMeta): self, ctx: commands.Context, user: discord.Member, - duration: Optional[commands.TimedeltaConverter] = timedelta(days=1), + duration: UserInputOptional[commands.TimedeltaConverter] = timedelta(days=1), days: Optional[int] = None, *, reason: str = None, diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index a64a4913e..9e73692ba 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -14,6 +14,7 @@ from redbot.cogs.warnings.helpers import ( ) from redbot.core import Config, checks, commands, modlog from redbot.core.bot import Red +from redbot.core.commands import UserInputOptional from redbot.core.i18n import Translator, cog_i18n from redbot.core.utils import AsyncIter from redbot.core.utils.chat_formatting import warning, pagify @@ -364,7 +365,7 @@ class Warnings(commands.Cog): self, ctx: commands.Context, user: discord.Member, - points: Optional[int] = 1, + points: UserInputOptional[int] = 1, *, reason: str, ): diff --git a/redbot/core/commands/converter.py b/redbot/core/commands/converter.py index a9b8e2950..2ad94e678 100644 --- a/redbot/core/commands/converter.py +++ b/redbot/core/commands/converter.py @@ -6,7 +6,6 @@ This module contains useful functions and classes for command argument conversio Some of the converters within are included provisionaly and are marked as such. """ import functools -import os import re import warnings from datetime import timedelta @@ -22,6 +21,7 @@ from typing import ( TypeVar, Literal as Literal, Any, + Union as UserInputOptional, ) import discord @@ -387,9 +387,6 @@ if not TYPE_CHECKING: This can be used instead of `typing.Optional` to avoid discord.py special casing the conversion behavior. - .. warning:: - This converter class is still provisional. - .. seealso:: The `ignore_optional_for_conversion` option of commands. """ @@ -400,34 +397,18 @@ if not TYPE_CHECKING: return key -_T_OPT = TypeVar("_T_OPT", bound=Type) +_T = TypeVar("_T") -if TYPE_CHECKING or os.getenv("BUILDING_DOCS", False): - - class UserInputOptional(Generic[_T_OPT]): - """ - This can be used when user input should be converted as discord.py - treats `typing.Optional`, but the type should not be equivalent to - ``typing.Union[DesiredType, None]`` for type checking. - - - .. warning:: - This converter class is still provisional. - - This class may not play well with mypy yet - and may still require you guard this in a - type checking conditional import vs the desired types - - We're aware and looking into improving this. - """ - - def __class_getitem__(cls, key: _T_OPT) -> _T_OPT: - if isinstance(key, tuple): - raise TypeError("Must only provide a single type to Optional") - return key - - -else: +if not TYPE_CHECKING: + #: This can be used when user input should be converted as discord.py + #: treats `typing.Optional`, but the type should not be equivalent to + #: ``typing.Union[DesiredType, None]`` for type checking. + #: + #: Note: In type checking context, this type hint can be passed + #: multiple types, but such usage is not supported and will fail at runtime + #: + #: .. warning:: + #: This converter class is still provisional. UserInputOptional = Optional