diff --git a/changelog/1120.bugfix.rst b/changelog/1120.bugfix.rst new file mode 100644 index 0000000000..146ac4a8de --- /dev/null +++ b/changelog/1120.bugfix.rst @@ -0,0 +1 @@ +|commands| Fix edge case in evaluation of multiple identical annotations with forwardrefs in a single signature. diff --git a/changelog/1123.bugfix.rst b/changelog/1123.bugfix.rst new file mode 100644 index 0000000000..625f275381 --- /dev/null +++ b/changelog/1123.bugfix.rst @@ -0,0 +1 @@ +Fix :meth:`Thread.permissions_for` not working in some cases due to an incorrect import. diff --git a/changelog/1126.doc.rst b/changelog/1126.doc.rst new file mode 100644 index 0000000000..44fa13bf31 --- /dev/null +++ b/changelog/1126.doc.rst @@ -0,0 +1 @@ +Make all "Supported Operations" container elements collapsible. diff --git a/disnake/activity.py b/disnake/activity.py index a213bf5a75..92460cd35d 100644 --- a/disnake/activity.py +++ b/disnake/activity.py @@ -404,7 +404,7 @@ class Game(BaseActivity): This is typically displayed via **Playing** on the official Discord client. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -487,7 +487,7 @@ class Streaming(BaseActivity): This is typically displayed via **Streaming** on the official Discord client. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -597,7 +597,7 @@ def __hash__(self) -> int: class Spotify(_BaseActivity): """Represents a Spotify listening activity from Discord. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -770,7 +770,7 @@ def party_id(self) -> str: class CustomActivity(BaseActivity): """Represents a Custom activity from Discord. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/asset.py b/disnake/asset.py index fc8fa6c7ea..fad72c79ce 100644 --- a/disnake/asset.py +++ b/disnake/asset.py @@ -164,7 +164,7 @@ async def to_file( class Asset(AssetMixin): """Represents a CDN asset on Discord. - .. container:: operations + .. collapse:: operations .. describe:: str(x) diff --git a/disnake/audit_logs.py b/disnake/audit_logs.py index 9d45912cd9..e8ab022edf 100644 --- a/disnake/audit_logs.py +++ b/disnake/audit_logs.py @@ -517,7 +517,7 @@ class AuditLogEntry(Hashable): You can retrieve these via :meth:`Guild.audit_logs`, or via the :func:`on_audit_log_entry_create` event. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/channel.py b/disnake/channel.py index 263735e24e..7eef52b942 100644 --- a/disnake/channel.py +++ b/disnake/channel.py @@ -103,7 +103,7 @@ async def _single_delete_strategy(messages: Iterable[Message]) -> None: class TextChannel(disnake.abc.Messageable, disnake.abc.GuildChannel, Hashable): """Represents a Discord guild text channel. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1217,7 +1217,7 @@ def permissions_for( class VoiceChannel(disnake.abc.Messageable, VocalGuildChannel): """Represents a Discord guild voice channel. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1871,7 +1871,7 @@ class StageChannel(disnake.abc.Messageable, VocalGuildChannel): .. versionadded:: 1.7 - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2696,7 +2696,7 @@ class CategoryChannel(disnake.abc.GuildChannel, Hashable): These are useful to group channels to logical compartments. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -3145,7 +3145,7 @@ class ForumChannel(disnake.abc.GuildChannel, Hashable): .. versionadded:: 2.5 - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -4184,7 +4184,7 @@ def get_tag_by_name(self, name: str, /) -> Optional[ForumTag]: class DMChannel(disnake.abc.Messageable, Hashable): """Represents a Discord direct message channel. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -4347,7 +4347,7 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage: class GroupChannel(disnake.abc.Messageable, Hashable): """Represents a Discord group channel. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -4506,7 +4506,7 @@ class PartialMessageable(disnake.abc.Messageable, Hashable): .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/colour.py b/disnake/colour.py index 82e8ef1bb3..4bd6585ea2 100644 --- a/disnake/colour.py +++ b/disnake/colour.py @@ -22,7 +22,7 @@ class Colour: There is an alias for this called Color. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/embeds.py b/disnake/embeds.py index 22ae7398af..1866d8d7eb 100644 --- a/disnake/embeds.py +++ b/disnake/embeds.py @@ -112,7 +112,7 @@ class _EmbedAuthorProxy(Sized, Protocol): class Embed: """Represents a Discord embed. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/emoji.py b/disnake/emoji.py index fb5ee1c3b4..0f3d02c27d 100644 --- a/disnake/emoji.py +++ b/disnake/emoji.py @@ -28,7 +28,7 @@ class Emoji(_EmojiTag, AssetMixin): Depending on the way this object was created, some of the attributes can have a value of ``None``. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/ext/commands/common_bot_base.py b/disnake/ext/commands/common_bot_base.py index 841c3df837..f0d8fd5566 100644 --- a/disnake/ext/commands/common_bot_base.py +++ b/disnake/ext/commands/common_bot_base.py @@ -4,6 +4,7 @@ import asyncio import collections.abc +import importlib.machinery import importlib.util import logging import os @@ -19,8 +20,6 @@ from .cog import Cog if TYPE_CHECKING: - import importlib.machinery - from ._types import CoroFunc from .bot import AutoShardedBot, AutoShardedInteractionBot, Bot, InteractionBot from .help import HelpCommand diff --git a/disnake/ext/commands/flag_converter.py b/disnake/ext/commands/flag_converter.py index 39a4b54808..37c97936c0 100644 --- a/disnake/ext/commands/flag_converter.py +++ b/disnake/ext/commands/flag_converter.py @@ -435,7 +435,7 @@ class FlagConverter(metaclass=FlagsMeta): how this converter works, check the appropriate :ref:`documentation `. - .. container:: operations + .. collapse:: operations .. describe:: iter(x) diff --git a/disnake/ext/commands/flags.py b/disnake/ext/commands/flags.py index ade3e79182..866566af3b 100644 --- a/disnake/ext/commands/flags.py +++ b/disnake/ext/commands/flags.py @@ -25,7 +25,7 @@ class CommandSyncFlags(BaseFlags): .. versionadded:: 2.7 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/ext/commands/help.py b/disnake/ext/commands/help.py index 25a8c247dc..5841a8ba11 100644 --- a/disnake/ext/commands/help.py +++ b/disnake/ext/commands/help.py @@ -48,7 +48,7 @@ class Paginator: """A class that aids in paginating code blocks for Discord messages. - .. container:: operations + .. collapse:: operations .. describe:: len(x) diff --git a/disnake/flags.py b/disnake/flags.py index 66b9b4b369..63fc6bf2c6 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -329,7 +329,7 @@ class SystemChannelFlags(BaseFlags, inverted=True): to enable or disable. Arguments are applied in order, similar to :class:`Permissions`. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -491,7 +491,7 @@ class MessageFlags(BaseFlags): See :class:`SystemChannelFlags`. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -681,7 +681,7 @@ def is_voice_message(self): class PublicUserFlags(BaseFlags): """Wraps up the Discord User Public flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -930,7 +930,7 @@ class Intents(BaseFlags): .. versionadded:: 1.5 - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1617,7 +1617,7 @@ class MemberCacheFlags(BaseFlags): .. versionadded:: 1.5 - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1793,7 +1793,7 @@ def _voice_only(self): class ApplicationFlags(BaseFlags): """Wraps up the Discord Application flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1968,7 +1968,7 @@ def application_command_badge(self): class ChannelFlags(BaseFlags): """Wraps up the Discord Channel flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2081,7 +2081,7 @@ def require_tag(self): class AutoModKeywordPresets(ListBaseFlags): """Wraps up the pre-defined auto moderation keyword lists, provided by Discord. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2194,7 +2194,7 @@ def slurs(self): class MemberFlags(BaseFlags): """Wraps up Discord Member flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2296,7 +2296,7 @@ def started_onboarding(self): class RoleFlags(BaseFlags): """Wraps up Discord Role flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2376,7 +2376,7 @@ def in_prompt(self): class AttachmentFlags(BaseFlags): """Wraps up Discord Attachment flags. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/guild.py b/disnake/guild.py index 888c7518d4..3927992fb5 100644 --- a/disnake/guild.py +++ b/disnake/guild.py @@ -130,7 +130,7 @@ class Guild(Hashable): This is referred to as a "server" in the official Discord UI. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/guild_scheduled_event.py b/disnake/guild_scheduled_event.py index a9739b217a..63b23620fe 100644 --- a/disnake/guild_scheduled_event.py +++ b/disnake/guild_scheduled_event.py @@ -77,7 +77,7 @@ class GuildScheduledEvent(Hashable): .. versionadded:: 2.3 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/invite.py b/disnake/invite.py index 2d95ea6d8a..a936c832b1 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -48,7 +48,7 @@ class PartialInviteChannel: guild the :class:`Invite` resolves to. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -137,7 +137,7 @@ class PartialInviteGuild: This model will be given when the user is not part of the guild the :class:`Invite` resolves to. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -256,7 +256,7 @@ class Invite(Hashable): Depending on the way this object was created, some of the attributes can have a value of ``None`` (see table below). - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/member.py b/disnake/member.py index 25886079eb..fb1a98e6c8 100644 --- a/disnake/member.py +++ b/disnake/member.py @@ -212,7 +212,7 @@ class Member(disnake.abc.Messageable, _UserTag): This implements a lot of the functionality of :class:`User`. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/message.py b/disnake/message.py index 21f59e269e..92aba532c7 100644 --- a/disnake/message.py +++ b/disnake/message.py @@ -220,7 +220,7 @@ async def _edit_handler( class Attachment(Hashable): """Represents an attachment from Discord. - .. container:: operations + .. collapse:: operations .. describe:: str(x) @@ -766,7 +766,7 @@ def flatten_handlers(cls): class Message(Hashable): """Represents a message from Discord. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -2177,7 +2177,7 @@ class PartialMessage(Hashable): .. versionadded:: 1.6 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/object.py b/disnake/object.py index 9af6a758b7..cd3048b6b1 100644 --- a/disnake/object.py +++ b/disnake/object.py @@ -29,7 +29,7 @@ class Object(Hashable): receive this class rather than the actual data class. These cases are extremely rare. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/partial_emoji.py b/disnake/partial_emoji.py index ab124d28e1..92656bb314 100644 --- a/disnake/partial_emoji.py +++ b/disnake/partial_emoji.py @@ -38,7 +38,7 @@ class PartialEmoji(_EmojiTag, AssetMixin): - "Raw" data events such as :func:`on_raw_reaction_add` - Custom emoji that the bot cannot see from e.g. :attr:`Message.reactions` - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/permissions.py b/disnake/permissions.py index 8046f14d4a..a7df815caa 100644 --- a/disnake/permissions.py +++ b/disnake/permissions.py @@ -76,7 +76,7 @@ class Permissions(BaseFlags): You can now use keyword arguments to initialize :class:`Permissions` similar to :meth:`update`. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1036,7 +1036,7 @@ class PermissionOverwrite: The values supported by this are the same as :class:`Permissions` with the added possibility of it being set to ``None``. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/reaction.py b/disnake/reaction.py index 5a3c784627..0720759f6a 100644 --- a/disnake/reaction.py +++ b/disnake/reaction.py @@ -22,7 +22,7 @@ class Reaction: Depending on the way this object was created, some of the attributes can have a value of ``None``. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/role.py b/disnake/role.py index addd6b7551..89fa55804f 100644 --- a/disnake/role.py +++ b/disnake/role.py @@ -140,7 +140,7 @@ def __repr__(self) -> str: class Role(Hashable): """Represents a Discord role in a :class:`Guild`. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/stage_instance.py b/disnake/stage_instance.py index 08f50dc3e1..deff882916 100644 --- a/disnake/stage_instance.py +++ b/disnake/stage_instance.py @@ -24,7 +24,7 @@ class StageInstance(Hashable): .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/sticker.py b/disnake/sticker.py index 01ce53b9d3..0d94c1ebcc 100644 --- a/disnake/sticker.py +++ b/disnake/sticker.py @@ -44,7 +44,7 @@ class StickerPack(Hashable): .. versionchanged:: 2.8 :attr:`cover_sticker_id`, :attr:`cover_sticker` and :attr:`banner` are now optional. - .. container:: operations + .. collapse:: operations .. describe:: str(x) @@ -163,7 +163,7 @@ class StickerItem(_StickerTag): .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: str(x) @@ -226,7 +226,7 @@ class Sticker(_StickerTag): .. versionadded:: 1.6 - .. container:: operations + .. collapse:: operations .. describe:: str(x) @@ -283,7 +283,7 @@ class StandardSticker(Sticker): .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: str(x) @@ -362,7 +362,7 @@ class GuildSticker(Sticker): .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: str(x) diff --git a/disnake/team.py b/disnake/team.py index 1034904cd9..dd0ee48d76 100644 --- a/disnake/team.py +++ b/disnake/team.py @@ -77,7 +77,7 @@ def owner(self) -> Optional[TeamMember]: class TeamMember(BaseUser): """Represents a team member in a team. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/threads.py b/disnake/threads.py index 2457c5a879..fb0b2add92 100644 --- a/disnake/threads.py +++ b/disnake/threads.py @@ -12,6 +12,7 @@ from .flags import ChannelFlags from .mixins import Hashable from .partial_emoji import PartialEmoji, _EmojiTag +from .permissions import Permissions from .utils import MISSING, _get_as_snowflake, _unique, parse_time, snowflake_time __all__ = ( @@ -31,7 +32,6 @@ from .guild import Guild from .member import Member from .message import Message, PartialMessage - from .permissions import Permissions from .role import Role from .state import ConnectionState from .types.snowflake import SnowflakeList @@ -54,7 +54,7 @@ class Thread(Messageable, Hashable): """Represents a Discord thread. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1018,7 +1018,7 @@ def _pop_member(self, member_id: int) -> Optional[ThreadMember]: class ThreadMember(Hashable): """Represents a Discord thread member. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -1092,7 +1092,7 @@ def thread(self) -> Thread: class ForumTag(Hashable): """Represents a tag for threads in forum channels. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/ui/action_row.py b/disnake/ui/action_row.py index b8473badb0..fe7244a776 100644 --- a/disnake/ui/action_row.py +++ b/disnake/ui/action_row.py @@ -91,7 +91,7 @@ class ActionRow(Generic[UIComponentT]): """Represents a UI action row. Useful for lower level component manipulation. - .. container:: operations + .. collapse:: operations .. describe:: x[i] diff --git a/disnake/user.py b/disnake/user.py index b2b05acb54..4326016100 100644 --- a/disnake/user.py +++ b/disnake/user.py @@ -281,7 +281,7 @@ def mentioned_in(self, message: Message) -> bool: class ClientUser(BaseUser): """Represents your Discord user. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -419,7 +419,7 @@ async def edit( class User(BaseUser, disnake.abc.Messageable): """Represents a Discord user. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/utils.py b/disnake/utils.py index 95f35003ce..1d06f137d2 100644 --- a/disnake/utils.py +++ b/disnake/utils.py @@ -1134,13 +1134,14 @@ def evaluate_annotation( if implicit_str and isinstance(tp, str): if tp in cache: return cache[tp] - evaluated = ( - eval( # noqa: PGH001, S307 # this is how annotations are supposed to be unstringifed - tp, globals, locals - ) - ) + + # this is how annotations are supposed to be unstringifed + evaluated = eval(tp, globals, locals) # noqa: PGH001, S307 + # recurse to resolve nested args further + evaluated = evaluate_annotation(evaluated, globals, locals, cache) + cache[tp] = evaluated - return evaluate_annotation(evaluated, globals, locals, cache) + return evaluated if hasattr(tp, "__args__"): implicit_str = True diff --git a/disnake/voice_region.py b/disnake/voice_region.py index 6c957d5e04..b08689db5b 100644 --- a/disnake/voice_region.py +++ b/disnake/voice_region.py @@ -14,7 +14,7 @@ class VoiceRegion: """Represents a Discord voice region. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/webhook/async_.py b/disnake/webhook/async_.py index edd9ec3dcd..4bd6f4b2d1 100644 --- a/disnake/webhook/async_.py +++ b/disnake/webhook/async_.py @@ -1034,7 +1034,7 @@ async def foo(): For a synchronous counterpart, see :class:`SyncWebhook`. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/webhook/sync.py b/disnake/webhook/sync.py index 0d2ba42b6c..b1debb9cf3 100644 --- a/disnake/webhook/sync.py +++ b/disnake/webhook/sync.py @@ -510,7 +510,7 @@ class SyncWebhook(BaseWebhook): For an asynchronous counterpart, see :class:`Webhook`. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/disnake/widget.py b/disnake/widget.py index d5056d82dc..4293985a36 100644 --- a/disnake/widget.py +++ b/disnake/widget.py @@ -34,7 +34,7 @@ class WidgetChannel: """Represents a "partial" widget channel. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -89,7 +89,7 @@ def created_at(self) -> datetime.datetime: class WidgetMember(BaseUser): """Represents a "partial" member of the widget's guild. - .. container:: operations + .. collapse:: operations .. describe:: x == y @@ -262,7 +262,7 @@ async def edit( class Widget: """Represents a :class:`Guild` widget. - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/docs/_static/style.css b/docs/_static/style.css index a54a5ecf52..b89e43a930 100644 --- a/docs/_static/style.css +++ b/docs/_static/style.css @@ -1313,21 +1313,20 @@ rect.highlighted { fill: var(--highlighted-text); } -.container.operations { +details.operations { padding: 10px; border: 1px solid var(--codeblock-border); margin-bottom: 20px; } -.container.operations::before { - content: 'Supported Operations'; - color: var(--main-big-headers-text); - display: block; - padding-bottom: 0.5em; +details.operations dl { + margin-top: 15px; + margin-bottom: 15px; } -.container.operations > dl.describe > dt { - background-color: var(--api-entry-background); +details.operations > summary::after { + content: 'Supported Operations'; + color: var(--main-big-headers-text); } .table-wrapper { diff --git a/docs/api/audit_logs.rst b/docs/api/audit_logs.rst index 1052c610b4..e4cb0573d2 100644 --- a/docs/api/audit_logs.rst +++ b/docs/api/audit_logs.rst @@ -93,7 +93,7 @@ AuditLogDiff on the action being done, check the documentation for :class:`AuditLogAction`, otherwise check the documentation below for all attributes that are possible. - .. container:: operations + .. collapse:: operations .. describe:: iter(diff) diff --git a/docs/api/guilds.rst b/docs/api/guilds.rst index 85f8d3d494..72a7ad6f84 100644 --- a/docs/api/guilds.rst +++ b/docs/api/guilds.rst @@ -131,7 +131,7 @@ VerificationLevel Specifies a :class:`Guild`\'s verification level, which is the criteria in which a member must meet before being able to send messages to the guild. - .. container:: operations + .. collapse:: operations .. versionadded:: 2.0 @@ -180,9 +180,7 @@ NotificationLevel Specifies whether a :class:`Guild` has notifications on for all messages or mentions only by default. - .. container:: operations - - .. versionadded:: 2.0 + .. collapse:: operations .. describe:: x == y @@ -219,9 +217,7 @@ ContentFilter learning algorithms that Discord uses to detect if an image contains NSFW content. - .. container:: operations - - .. versionadded:: 2.0 + .. collapse:: operations .. describe:: x == y @@ -261,7 +257,7 @@ NSFWLevel .. versionadded:: 2.0 - .. container:: operations + .. collapse:: operations .. describe:: x == y diff --git a/docs/api/messages.rst b/docs/api/messages.rst index 123260f167..3031d955d9 100644 --- a/docs/api/messages.rst +++ b/docs/api/messages.rst @@ -188,14 +188,14 @@ MessageType Specifies the type of :class:`Message`. This is used to denote if a message is to be interpreted as a system message or a regular message. - .. container:: operations + .. collapse:: operations - .. describe:: x == y + .. describe:: x == y - Checks if two messages are equal. - .. describe:: x != y + Checks if two messages are equal. + .. describe:: x != y - Checks if two messages are not equal. + Checks if two messages are not equal. .. attribute:: default diff --git a/docs/api/misc.rst b/docs/api/misc.rst index c49854c289..83ee3298d8 100644 --- a/docs/api/misc.rst +++ b/docs/api/misc.rst @@ -18,7 +18,7 @@ AsyncIterator Represents the "AsyncIterator" concept. Note that no such class exists, it is purely abstract. - .. container:: operations + .. collapse:: operations .. describe:: async for x in y diff --git a/docs/conf.py b/docs/conf.py index 355f977465..5944191079 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,6 +52,7 @@ "exception_hierarchy", "attributetable", "resourcelinks", + "collapse", "nitpick_file_ignorer", ] diff --git a/docs/extensions/attributetable.py b/docs/extensions/attributetable.py index 1a66a0c026..d718c73604 100644 --- a/docs/extensions/attributetable.py +++ b/docs/extensions/attributetable.py @@ -14,12 +14,13 @@ from sphinx.util.docutils import SphinxDirective if TYPE_CHECKING: - from _types import SphinxExtensionMeta from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment from sphinx.util.typing import OptionSpec from sphinx.writers.html import HTMLTranslator + from ._types import SphinxExtensionMeta + class attributetable(nodes.General, nodes.Element): pass diff --git a/docs/extensions/builder.py b/docs/extensions/builder.py index 6f1a5493d4..5133af0f85 100644 --- a/docs/extensions/builder.py +++ b/docs/extensions/builder.py @@ -8,12 +8,13 @@ from sphinx.environment.adapters.indexentries import IndexEntries if TYPE_CHECKING: - from _types import SphinxExtensionMeta from docutils import nodes from sphinx.application import Sphinx from sphinx.config import Config from sphinx.writers.html5 import HTML5Translator + from ._types import SphinxExtensionMeta + if TYPE_CHECKING: translator_base = HTML5Translator else: diff --git a/docs/extensions/collapse.py b/docs/extensions/collapse.py new file mode 100644 index 0000000000..cde568aea2 --- /dev/null +++ b/docs/extensions/collapse.py @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: MIT +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar + +from docutils import nodes +from docutils.parsers.rst import Directive, directives + +if TYPE_CHECKING: + from sphinx.application import Sphinx + from sphinx.util.typing import OptionSpec + from sphinx.writers.html import HTMLTranslator + + from ._types import SphinxExtensionMeta + + +class collapse(nodes.General, nodes.Element): + pass + + +def visit_collapse_node(self: HTMLTranslator, node: nodes.Element) -> None: + attrs = {"open": ""} if node["open"] else {} + self.body.append(self.starttag(node, "details", **attrs)) + self.body.append("") + + +def depart_collapse_node(self: HTMLTranslator, node: nodes.Element) -> None: + self.body.append("\n") + + +class CollapseDirective(Directive): + has_content = True + + optional_arguments = 1 + final_argument_whitespace = True + + option_spec: ClassVar[OptionSpec] = {"open": directives.flag} + + def run(self): + self.assert_has_content() + node = collapse( + "\n".join(self.content), + open="open" in self.options, + ) + + classes = directives.class_option(self.arguments[0] if self.arguments else "") + node["classes"].extend(classes) + + self.state.nested_parse(self.content, self.content_offset, node) + return [node] + + +def setup(app: Sphinx) -> SphinxExtensionMeta: + app.add_node(collapse, html=(visit_collapse_node, depart_collapse_node)) + app.add_directive("collapse", CollapseDirective) + + return { + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/extensions/exception_hierarchy.py b/docs/extensions/exception_hierarchy.py index 147a175af2..67040643ae 100644 --- a/docs/extensions/exception_hierarchy.py +++ b/docs/extensions/exception_hierarchy.py @@ -7,10 +7,11 @@ from docutils.parsers.rst import Directive if TYPE_CHECKING: - from _types import SphinxExtensionMeta from sphinx.application import Sphinx from sphinx.writers.html import HTMLTranslator + from ._types import SphinxExtensionMeta + class exception_hierarchy(nodes.General, nodes.Element): pass diff --git a/docs/extensions/fulltoc.py b/docs/extensions/fulltoc.py index 1d7523e52a..e35cd79514 100644 --- a/docs/extensions/fulltoc.py +++ b/docs/extensions/fulltoc.py @@ -31,7 +31,6 @@ from typing import TYPE_CHECKING, List, cast -from _types import SphinxExtensionMeta from docutils import nodes from sphinx import addnodes @@ -40,6 +39,8 @@ from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.environment import BuildEnvironment + from ._types import SphinxExtensionMeta + # {prefix: index_doc} mapping # Any document that matches `prefix` will use `index_doc`'s toctree instead. GROUPED_SECTIONS = {"api/": "api/index", "ext/commands/api/": "ext/commands/api/index"} diff --git a/docs/extensions/nitpick_file_ignorer.py b/docs/extensions/nitpick_file_ignorer.py index da967f9d92..cc9eab588f 100644 --- a/docs/extensions/nitpick_file_ignorer.py +++ b/docs/extensions/nitpick_file_ignorer.py @@ -7,9 +7,10 @@ from sphinx.util import logging as sphinx_logging if TYPE_CHECKING: - from _types import SphinxExtensionMeta from sphinx.application import Sphinx + from ._types import SphinxExtensionMeta + class NitpickFileIgnorer(logging.Filter): def __init__(self, app: Sphinx) -> None: diff --git a/docs/extensions/redirects.py b/docs/extensions/redirects.py index fea63483be..44d1c16bef 100644 --- a/docs/extensions/redirects.py +++ b/docs/extensions/redirects.py @@ -1,13 +1,16 @@ # SPDX-License-Identifier: MIT +from __future__ import annotations import json from pathlib import Path -from typing import Dict +from typing import TYPE_CHECKING, Dict -from _types import SphinxExtensionMeta from sphinx.application import Sphinx from sphinx.util.fileutil import copy_asset_file +if TYPE_CHECKING: + from ._types import SphinxExtensionMeta + SCRIPT_PATH = "_templates/api_redirect.js_t" diff --git a/docs/extensions/resourcelinks.py b/docs/extensions/resourcelinks.py index 76a57b4656..d93f6f2715 100644 --- a/docs/extensions/resourcelinks.py +++ b/docs/extensions/resourcelinks.py @@ -10,12 +10,13 @@ from sphinx.util.nodes import split_explicit_title if TYPE_CHECKING: - from _types import SphinxExtensionMeta from docutils.nodes import Node, system_message from docutils.parsers.rst.states import Inliner from sphinx.application import Sphinx from sphinx.util.typing import RoleFunction + from ._types import SphinxExtensionMeta + def make_link_role(resource_links: Dict[str, str]) -> RoleFunction: def role( diff --git a/pyproject.toml b/pyproject.toml index a2e0854836..984bdf767f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,7 +149,7 @@ select = [ # "RET", # flake8-return # "SIM", # flake8-simplify "TID251", # flake8-tidy-imports, replaces S404 - # "TCH", # flake8-type-checking + "TCH", # flake8-type-checking "RUF", # ruff specific exceptions "PT", # flake8-pytest-style "Q", # flake8-quotes @@ -198,6 +198,13 @@ ignore = [ # outer loop variables are overwritten by inner assignment target, these are mostly intentional "PLW2901", + # ignore imports that could be moved into type-checking blocks + # (no real advantage other than possibly avoiding cycles, + # but can be dangerous in places where we need to parse signatures) + "TCH001", + "TCH002", + "TCH003", + # temporary disables, to fix later "D205", # blank line required between summary and description "D401", # first line of docstring should be in imperative mood diff --git a/tests/ext/commands/test_core.py b/tests/ext/commands/test_core.py index 1d3076a845..2b29f51988 100644 --- a/tests/ext/commands/test_core.py +++ b/tests/ext/commands/test_core.py @@ -1,20 +1,10 @@ # SPDX-License-Identifier: MIT -from typing import TYPE_CHECKING +from typing_extensions import assert_type from disnake.ext import commands from tests.helpers import reveal_type -if TYPE_CHECKING: - from typing_extensions import assert_type - - # NOTE: using undocumented `expected_text` parameter of pyright instead of `assert_type`, - # as `assert_type` can't handle bound ParamSpecs - reveal_type( - 42, # type: ignore - expected_text="str", # type: ignore - ) - class CustomContext(commands.Context): ... diff --git a/tests/helpers.py b/tests/helpers.py index 2d5a4d8e41..8e22e0cd08 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -16,6 +16,15 @@ def reveal_type(*args, **kwargs) -> None: raise RuntimeError +if TYPE_CHECKING: + # NOTE: using undocumented `expected_text` parameter of pyright instead of `assert_type`, + # as `assert_type` can't handle bound ParamSpecs + reveal_type( + 42, # type: ignore # suppress "revealed type is ..." output + expected_text="str", # type: ignore # ensure the functionality we want still works as expected + ) + + CallableT = TypeVar("CallableT", bound=Callable) diff --git a/tests/test_utils.py b/tests/test_utils.py index 48ef75134a..a8f52e6b1f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -758,8 +758,8 @@ def test_normalise_optional_params(params, expected) -> None: ("Tuple[dict, List[Literal[42, 99]]]", Tuple[dict, List[Literal[42, 99]]], True), # 3.10 union syntax pytest.param( - "int | Literal[False]", - Union[int, Literal[False]], + "int | float", + Union[int, float], True, marks=pytest.mark.skipif(sys.version_info < (3, 10), reason="syntax requires py3.10"), ), diff --git a/tests/ui/test_action_row.py b/tests/ui/test_action_row.py index 9e72ecc3eb..f9c40ffedc 100644 --- a/tests/ui/test_action_row.py +++ b/tests/ui/test_action_row.py @@ -4,17 +4,20 @@ from unittest import mock import pytest +from typing_extensions import assert_type import disnake -from disnake.ui import ActionRow, Button, StringSelect, TextInput, WrappedComponent +from disnake.ui import ( + ActionRow, + Button, + MessageUIComponent, + ModalUIComponent, + StringSelect, + TextInput, + WrappedComponent, +) from disnake.ui.action_row import components_to_dict, components_to_rows -if TYPE_CHECKING: - from typing_extensions import assert_type - - from disnake.ui import MessageUIComponent, ModalUIComponent - - button1 = Button() button2 = Button() button3 = Button() @@ -133,9 +136,8 @@ def test_with_components(self) -> None: row_msg = ActionRow.with_message_components() assert list(row_msg.children) == [] - if TYPE_CHECKING: - assert_type(row_modal, ActionRow[ModalUIComponent]) - assert_type(row_msg, ActionRow[MessageUIComponent]) + assert_type(row_modal, ActionRow[ModalUIComponent]) + assert_type(row_msg, ActionRow[MessageUIComponent]) def test_rows_from_message(self) -> None: rows = [