Skip to content

Commit

Permalink
Merge branch 'master' into feature/app-subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
shiftinv authored Jan 3, 2024
2 parents 2aab8b4 + 6430c2b commit bac6567
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 60 deletions.
1 change: 1 addition & 0 deletions changelog/1085.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``applied_tags`` parameter to :meth:`Webhook.send`.
1 change: 1 addition & 0 deletions changelog/1128.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
|commands| Support Python 3.12's ``type`` statement and :class:`py:typing.TypeAliasType` annotations in command signatures.
1 change: 1 addition & 0 deletions changelog/1133.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
|commands| Fix erroneous :class:`LocalizationWarning`\s when using localized slash command parameters in cogs.
1 change: 1 addition & 0 deletions changelog/1134.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :class:`StandardSticker` to ``stickers`` parameter type annotation of :meth:`Messageable.send` and :meth:`ForumChannel.create_thread`.
1 change: 1 addition & 0 deletions changelog/1136.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update ``choices`` type in app commands to accept any :class:`~py:typing.Sequence` or :class:`~py:typing.Mapping`, instead of the more constrained :class:`list`/:class:`dict` types.
14 changes: 7 additions & 7 deletions disnake/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from .partial_emoji import PartialEmoji
from .permissions import PermissionOverwrite, Permissions
from .role import Role
from .sticker import GuildSticker, StickerItem
from .sticker import GuildSticker, StandardSticker, StickerItem
from .ui.action_row import components_to_dict
from .utils import _overload_with_permissions
from .voice_client import VoiceClient, VoiceProtocol
Expand Down Expand Up @@ -1429,7 +1429,7 @@ async def send(
tts: bool = ...,
embed: Embed = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
suppress_embeds: bool = ...,
Expand All @@ -1450,7 +1450,7 @@ async def send(
tts: bool = ...,
embed: Embed = ...,
files: List[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
suppress_embeds: bool = ...,
Expand All @@ -1471,7 +1471,7 @@ async def send(
tts: bool = ...,
embeds: List[Embed] = ...,
file: File = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
suppress_embeds: bool = ...,
Expand All @@ -1492,7 +1492,7 @@ async def send(
tts: bool = ...,
embeds: List[Embed] = ...,
files: List[File] = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
delete_after: float = ...,
nonce: Union[str, int] = ...,
suppress_embeds: bool = ...,
Expand All @@ -1514,7 +1514,7 @@ async def send(
embeds: Optional[List[Embed]] = None,
file: Optional[File] = None,
files: Optional[List[File]] = None,
stickers: Optional[Sequence[Union[GuildSticker, StickerItem]]] = None,
stickers: Optional[Sequence[Union[GuildSticker, StandardSticker, StickerItem]]] = None,
delete_after: Optional[float] = None,
nonce: Optional[Union[str, int]] = None,
suppress_embeds: Optional[bool] = None,
Expand Down Expand Up @@ -1567,7 +1567,7 @@ async def send(
files: List[:class:`.File`]
A list of files to upload. Must be a maximum of 10.
This cannot be mixed with the ``file`` parameter.
stickers: Sequence[Union[:class:`.GuildSticker`, :class:`.StickerItem`]]
stickers: Sequence[Union[:class:`.GuildSticker`, :class:`.StandardSticker`, :class:`.StickerItem`]]
A list of stickers to upload. Must be a maximum of 3.
.. versionadded:: 2.0
Expand Down
25 changes: 14 additions & 11 deletions disnake/app_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import math
import re
from abc import ABC
from typing import TYPE_CHECKING, ClassVar, Dict, List, Mapping, Optional, Tuple, Union
from typing import TYPE_CHECKING, ClassVar, List, Mapping, Optional, Sequence, Tuple, Union

from .enums import (
ApplicationCommandPermissionType,
Expand Down Expand Up @@ -37,10 +37,10 @@
)

Choices = Union[
List["OptionChoice"],
List[ApplicationCommandOptionChoiceValue],
Dict[str, ApplicationCommandOptionChoiceValue],
List[Localized[str]],
Sequence["OptionChoice"],
Sequence[ApplicationCommandOptionChoiceValue],
Mapping[str, ApplicationCommandOptionChoiceValue],
Sequence[Localized[str]],
]

APIApplicationCommand = Union["APIUserCommand", "APIMessageCommand", "APISlashCommand"]
Expand Down Expand Up @@ -179,8 +179,8 @@ class Option:
The option type, e.g. :class:`OptionType.user`.
required: :class:`bool`
Whether this option is required.
choices: Union[List[:class:`OptionChoice`], List[Union[:class:`str`, :class:`int`]], Dict[:class:`str`, Union[:class:`str`, :class:`int`]]]
The list of option choices.
choices: Union[Sequence[:class:`OptionChoice`], Sequence[Union[:class:`str`, :class:`int`, :class:`float`]], Mapping[:class:`str`, Union[:class:`str`, :class:`int`, :class:`float`]]]
The pre-defined choices for this option.
options: List[:class:`Option`]
The list of sub options. Normally you don't have to specify it directly,
instead consider using ``@main_cmd.sub_command`` or ``@main_cmd.sub_command_group`` decorators.
Expand Down Expand Up @@ -214,7 +214,7 @@ class Option:
required: :class:`bool`
Whether this option is required.
choices: List[:class:`OptionChoice`]
The list of option choices.
The list of pre-defined choices.
options: List[:class:`Option`]
The list of sub options. Normally you don't have to specify it directly,
instead consider using ``@main_cmd.sub_command`` or ``@main_cmd.sub_command_group`` decorators.
Expand Down Expand Up @@ -304,6 +304,9 @@ def __init__(
if autocomplete:
raise TypeError("can not specify both choices and autocomplete args")

if isinstance(choices, str): # str matches `Sequence[str]`, but isn't meant to be used
raise TypeError("choices argument should be a list/sequence or dict, not str")

if isinstance(choices, Mapping):
self.choices = [OptionChoice(name, value) for name, value in choices.items()]
else:
Expand Down Expand Up @@ -370,7 +373,7 @@ def from_dict(cls, data: ApplicationCommandOptionPayload) -> Option:
def add_choice(
self,
name: LocalizedRequired,
value: Union[str, int],
value: ApplicationCommandOptionChoiceValue,
) -> None:
"""Adds an OptionChoice to the list of current choices,
parameters are the same as for :class:`OptionChoice`.
Expand All @@ -388,7 +391,7 @@ def add_option(
description: LocalizedOptional = None,
type: Optional[OptionType] = None,
required: bool = False,
choices: Optional[List[OptionChoice]] = None,
choices: Optional[Choices] = None,
options: Optional[list] = None,
channel_types: Optional[List[ChannelType]] = None,
autocomplete: bool = False,
Expand Down Expand Up @@ -884,7 +887,7 @@ def add_option(
description: LocalizedOptional = None,
type: Optional[OptionType] = None,
required: bool = False,
choices: Optional[List[OptionChoice]] = None,
choices: Optional[Choices] = None,
options: Optional[list] = None,
channel_types: Optional[List[ChannelType]] = None,
autocomplete: bool = False,
Expand Down
14 changes: 7 additions & 7 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
from .message import AllowedMentions, Message, PartialMessage
from .role import Role
from .state import ConnectionState
from .sticker import GuildSticker, StickerItem
from .sticker import GuildSticker, StandardSticker, StickerItem
from .threads import AnyThreadArchiveDuration, ThreadType
from .types.channel import (
CategoryChannel as CategoryChannelPayload,
Expand Down Expand Up @@ -3791,7 +3791,7 @@ async def create_thread(
file: File = ...,
suppress_embeds: bool = ...,
flags: MessageFlags = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
allowed_mentions: AllowedMentions = ...,
view: View = ...,
components: Components = ...,
Expand All @@ -3812,7 +3812,7 @@ async def create_thread(
files: List[File] = ...,
suppress_embeds: bool = ...,
flags: MessageFlags = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
allowed_mentions: AllowedMentions = ...,
view: View = ...,
components: Components = ...,
Expand All @@ -3833,7 +3833,7 @@ async def create_thread(
file: File = ...,
suppress_embeds: bool = ...,
flags: MessageFlags = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
allowed_mentions: AllowedMentions = ...,
view: View = ...,
components: Components = ...,
Expand All @@ -3854,7 +3854,7 @@ async def create_thread(
files: List[File] = ...,
suppress_embeds: bool = ...,
flags: MessageFlags = ...,
stickers: Sequence[Union[GuildSticker, StickerItem]] = ...,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = ...,
allowed_mentions: AllowedMentions = ...,
view: View = ...,
components: Components = ...,
Expand All @@ -3876,7 +3876,7 @@ async def create_thread(
files: List[File] = MISSING,
suppress_embeds: bool = MISSING,
flags: MessageFlags = MISSING,
stickers: Sequence[Union[GuildSticker, StickerItem]] = MISSING,
stickers: Sequence[Union[GuildSticker, StandardSticker, StickerItem]] = MISSING,
allowed_mentions: AllowedMentions = MISSING,
view: View = MISSING,
components: Components[MessageUIComponent] = MISSING,
Expand Down Expand Up @@ -3940,7 +3940,7 @@ async def create_thread(
files: List[:class:`.File`]
A list of files to upload. Must be a maximum of 10.
This cannot be mixed with the ``file`` parameter.
stickers: Sequence[Union[:class:`.GuildSticker`, :class:`.StickerItem`]]
stickers: Sequence[Union[:class:`.GuildSticker`, :class:`.StandardSticker`, :class:`.StickerItem`]]
A list of stickers to upload. Must be a maximum of 3.
allowed_mentions: :class:`.AllowedMentions`
Controls the mentions being processed in this message. If this is
Expand Down
11 changes: 6 additions & 5 deletions disnake/ext/commands/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import asyncio
import collections.abc
import copy
import inspect
import itertools
import math
Expand Down Expand Up @@ -451,8 +452,8 @@ class ParamInfo:
.. versionchanged:: 2.5
Added support for localizations.
choices: Union[List[:class:`.OptionChoice`], List[Union[:class:`str`, :class:`int`]], Dict[:class:`str`, Union[:class:`str`, :class:`int`]]]
The list of choices of this slash command option.
choices: Union[Sequence[:class:`.OptionChoice`], Sequence[Union[:class:`str`, :class:`int`, :class:`float`]], Mapping[:class:`str`, Union[:class:`str`, :class:`int`, :class:`float`]]]
The pre-defined choices for this option.
ge: :class:`float`
The lowest allowed value for this option.
le: :class:`float`
Expand Down Expand Up @@ -554,7 +555,7 @@ def copy(self) -> Self:
ins.converter = self.converter
ins.convert_default = self.convert_default
ins.autocomplete = self.autocomplete
ins.choices = self.choices.copy()
ins.choices = copy.copy(self.choices)
ins.type = self.type
ins.channel_types = self.channel_types.copy()
ins.max_value = self.max_value
Expand Down Expand Up @@ -1155,8 +1156,8 @@ def Param(
.. versionchanged:: 2.5
Added support for localizations.
choices: Union[List[:class:`.OptionChoice`], List[Union[:class:`str`, :class:`int`]], Dict[:class:`str`, Union[:class:`str`, :class:`int`]]]
A list of choices for this option.
choices: Union[Sequence[:class:`.OptionChoice`], Sequence[Union[:class:`str`, :class:`int`, :class:`float`]], Mapping[:class:`str`, Union[:class:`str`, :class:`int`, :class:`float`]]]
The pre-defined choices for this slash command option.
converter: Callable[[:class:`.ApplicationCommandInteraction`, Any], Any]
A function that will convert the original input to a desired format.
Kwarg aliases: ``conv``.
Expand Down
7 changes: 6 additions & 1 deletion disnake/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,20 @@ def _copy(self) -> LocalizationValue:
def data(self) -> Optional[Dict[str, str]]:
"""Optional[Dict[:class:`str`, :class:`str`]]: A dict with a locale -> localization mapping, if available."""
if self._data is MISSING:
# This will happen when `_link(store)` hasn't been called yet, which *shouldn't* occur under normal circumstances.
warnings.warn(
f"value ('{self._key}') was never localized, this is likely a library bug",
f"Localization value ('{self._key}') was never linked to bot; this may be a library bug.",
LocalizationWarning,
stacklevel=2,
)
return None
return self._data

def __eq__(self, other) -> bool:
# if both are pending, compare keys instead
if self._data is MISSING and other._data is MISSING:
return self._key == other._key

d1 = self.data
d2 = other.data
# consider values equal if they're both falsy, or actually equal
Expand Down
7 changes: 5 additions & 2 deletions disnake/interactions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1265,8 +1265,8 @@ async def autocomplete(self, *, choices: Choices) -> None:
Parameters
----------
choices: Union[List[:class:`OptionChoice`], List[Union[:class:`str`, :class:`int`]], Dict[:class:`str`, Union[:class:`str`, :class:`int`]]]
The list of choices to suggest.
choices: Union[Sequence[:class:`OptionChoice`], Sequence[Union[:class:`str`, :class:`int`, :class:`float`]], Mapping[:class:`str`, Union[:class:`str`, :class:`int`, :class:`float`]]]
The choices to suggest.
Raises
------
Expand All @@ -1282,6 +1282,9 @@ async def autocomplete(self, *, choices: Choices) -> None:
if isinstance(choices, Mapping):
choices_data = [{"name": n, "value": v} for n, v in choices.items()]
else:
if isinstance(choices, str): # str matches `Sequence[str]`, but isn't meant to be used
raise TypeError("choices argument should be a list/sequence or dict, not str")

choices_data = []
value: ApplicationCommandOptionChoicePayload
i18n = self._parent.client.i18n
Expand Down
48 changes: 41 additions & 7 deletions disnake/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,24 @@ def normalise_optional_params(parameters: Iterable[Any]) -> Tuple[Any, ...]:
return tuple(p for p in parameters if p is not none_cls) + (none_cls,)


def _resolve_typealiastype(
tp: Any, globals: Dict[str, Any], locals: Dict[str, Any], cache: Dict[str, Any]
):
# Use __module__ to get the (global) namespace in which the type alias was defined.
if mod := sys.modules.get(tp.__module__):
mod_globals = mod.__dict__
if mod_globals is not globals or mod_globals is not locals:
# if the namespace changed (usually when a TypeAliasType was imported from a different module),
# drop the cache since names can resolve differently now
cache = {}
globals = locals = mod_globals

# Accessing `__value__` automatically evaluates the type alias in the annotation scope.
# (recurse to resolve possible forwardrefs, aliases, etc.)
return evaluate_annotation(tp.__value__, globals, locals, cache)


# FIXME: this should be split up into smaller functions for clarity and easier maintenance
def evaluate_annotation(
tp: Any,
globals: Dict[str, Any],
Expand All @@ -1147,23 +1165,31 @@ def evaluate_annotation(
cache[tp] = evaluated
return evaluated

# GenericAlias / UnionType
if hasattr(tp, "__args__"):
implicit_str = True
is_literal = False
orig_args = args = tp.__args__
if not hasattr(tp, "__origin__"):
if tp.__class__ is UnionType:
converted = Union[args] # type: ignore
converted = Union[tp.__args__] # type: ignore
return evaluate_annotation(converted, globals, locals, cache)

return tp
if tp.__origin__ is Union:

implicit_str = True
is_literal = False
orig_args = args = tp.__args__
orig_origin = origin = tp.__origin__

# origin can be a TypeAliasType too, resolve it and continue
if hasattr(origin, "__value__"):
origin = _resolve_typealiastype(origin, globals, locals, cache)

if origin is Union:
try:
if args.index(type(None)) != len(args) - 1:
args = normalise_optional_params(tp.__args__)
except ValueError:
pass
if tp.__origin__ is Literal:
if origin is Literal:
if not PY_310:
args = flatten_literal_params(tp.__args__)
implicit_str = False
Expand All @@ -1179,13 +1205,21 @@ def evaluate_annotation(
):
raise TypeError("Literal arguments must be of type str, int, bool, or NoneType.")

if origin != orig_origin:
# we can't use `copy_with` in this case, so just skip all of the following logic
return origin[evaluated_args]

if evaluated_args == orig_args:
return tp

try:
return tp.copy_with(evaluated_args)
except AttributeError:
return tp.__origin__[evaluated_args]
return origin[evaluated_args]

# TypeAliasType, 3.12+
if hasattr(tp, "__value__"):
return _resolve_typealiastype(tp, globals, locals, cache)

return tp

Expand Down
Loading

0 comments on commit bac6567

Please sign in to comment.