Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext.commands): add LargeRange #857

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions changelog/787.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
|commands| Add :class:`~ext.commands.LargeRange`, which is a version of :class:`~ext.commands.Range` that supports large numbers.
19 changes: 19 additions & 0 deletions disnake/ext/commands/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"PartialEmojiConversionFailure",
"BadBoolArgument",
"LargeIntConversionFailure",
"LargeFloatConversionFailure",
"MissingRole",
"BotMissingRole",
"MissingAnyRole",
Expand Down Expand Up @@ -551,6 +552,24 @@ def __init__(self, argument: str) -> None:
super().__init__(f"{argument} is not able to be converted to an integer")


class LargeFloatConversionFailure(BadArgument):
"""Exception raised when a large floating-point number argument was not able to be converted.

This inherits from :exc:`BadArgument`

.. versionadded:: 2.9

Attributes
----------
argument: :class:`str`
The argument that could not be converted to a floating-point number.
"""

def __init__(self, argument: str) -> None:
self.argument: str = argument
super().__init__(f"{argument} is not able to be converted to a floating-point number")


class DisabledCommand(CommandError):
"""Exception raised when the command being invoked is disabled.

Expand Down
63 changes: 56 additions & 7 deletions disnake/ext/commands/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@

__all__ = (
"Range",
"LargeRange",
"String",
"LargeInt",
"ParamInfo",
Expand Down Expand Up @@ -369,6 +370,16 @@ def __repr__(self) -> str:
return f"{type(self).__name__}[{a}, {b}]"


class LargeRange(Range):
"""Type depicting a range that can handle large numbers.

See :ref:`param_ranges` for more information.

.. versionadded:: 2.9

"""


class StringMeta(type):
"""Custom Generic implementation for String."""

Expand Down Expand Up @@ -529,6 +540,7 @@ def __init__(
self.min_length = min_length
self.max_length = max_length
self.large = large
self._original_large_type: Optional[Union[Type[int], Type[float]]] = None

@property
def required(self) -> bool:
Expand Down Expand Up @@ -615,11 +627,37 @@ async def verify_type(self, inter: ApplicationCommandInteraction, argument: Any)

async def convert_argument(self, inter: ApplicationCommandInteraction, argument: Any) -> Any:
"""Convert a value if a converter is given"""
if self.large:
try:
argument = int(argument)
except ValueError:
raise errors.LargeIntConversionFailure(argument) from None
if self.large and self._original_large_type:
if self._original_large_type == int:
try:
argument = int(argument)
except ValueError:
raise errors.LargeIntConversionFailure(argument) from None
elif self._original_large_type == float:
try:
argument = float(argument)
print(argument)
except ValueError:
raise errors.LargeFloatConversionFailure(argument) from None
else:
raise TypeError(f"Invalid large number type {self._original_large_type!r}!")

if (
self.min_value is not None
and self.max_value is not None
and (argument < self.min_value or argument > self.max_value)
):
raise errors.BadArgument(
f"Value for {self.name} must be in-between {self.min_value} and {self.max_value}!"
)
elif self.min_value is not None and argument < self.min_value:
raise errors.BadArgument(
f"Value for {self.name} must be greater than {self.min_value}!"
)
elif self.max_value is not None and argument > self.max_value:
raise errors.BadArgument(
f"Value for {self.name} must be less than {self.max_value}!"
)

if self.converter is None:
# TODO: Custom validators
Expand Down Expand Up @@ -679,6 +717,14 @@ def parse_annotation(self, annotation: Any, converter_mode: bool = False) -> boo
if isinstance(annotation, Range):
self.min_value = annotation.min_value
self.max_value = annotation.max_value

if (
isinstance(annotation, LargeRange)
and self.min_value < -(2**53)
or self.max_value > 2**53
):
self.large = True

annotation = annotation.underlying_type
if isinstance(annotation, String):
self.min_length = annotation.min_length
Expand All @@ -690,8 +736,11 @@ def parse_annotation(self, annotation: Any, converter_mode: bool = False) -> boo

if self.large:
self.type = str
if annotation is not int:
raise TypeError("Large integers must be annotated with int or LargeInt")
if annotation not in (int, float):
raise TypeError(
"Large integers or large floats must be annotated with int, LargeInt, float, or LargeRange!"
)
self._original_large_type = annotation
elif annotation in self.TYPES:
self.type = annotation
elif (
Expand Down
8 changes: 8 additions & 0 deletions docs/ext/commands/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ Range

.. autoclass:: Range

LargeRange
~~~~~~~~~~

.. autoclass:: disnake.ext.commands.LargeRange

String
~~~~~~

Expand Down Expand Up @@ -967,6 +972,9 @@ Exceptions
.. autoexception:: LargeIntConversionFailure
:members:

.. autoexception:: LargeFloatConversionFailure
:members:

.. autoexception:: MissingPermissions
:members:

Expand Down
3 changes: 3 additions & 0 deletions docs/ext/commands/slash_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ The type of the option is determined by the range bounds, with the option being
):
...


If you want to explicitly enable large numbers with range, then you can use a :class:`~ext.commands.LargeRange` annotation.

.. _type_checker_mypy_plugin:

.. note::
Expand Down