# -*- coding: utf-8 -*-
# Copyright (c) 2023-present tandemdude
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
__all__ = ["EMPTY", "FloatEnum", "StrEnum", "get_command_data", "maybe_await", "to_choices"]
import enum
import inspect
import sys
import typing as t
from collections.abc import Sequence
from lightbulb.internal import marker
if t.TYPE_CHECKING:
from lightbulb.commands import commands
from lightbulb.commands import groups
from lightbulb.commands import options
from lightbulb.internal import types
T = t.TypeVar("T")
EMPTY: t.Final[t.Any] = marker.Marker("EMPTY")
"""Placeholder object returned when attempting to get the value for an option on a class instead of an instance.
Example:
.. code-block:: python
class YourCommand(lightbulb.SlashCommand, ...):
option = lightbulb.string(...)
...
# The following will be True
YourCommand.option is lightbulb.utils.EMPTY
"""
[docs]
def get_command_data(
command: commands.CommandBase | type[commands.CommandBase] | groups.Group | groups.SubGroup,
) -> commands.CommandData:
"""
Utility method to get the command data dataclass for a command instance, command class, group, or subgroup.
Args:
command: The command instance, command class, group, or subgroup to get the command data for.
Returns:
:obj:`~lightbulb.commands.commands.CommandData`: Command data dataclass for the given command.
"""
return command._command_data
[docs]
async def maybe_await(item: types.MaybeAwaitable[T]) -> T:
"""
Await the given item if it is a coroutine, otherwise just return the given item.
Args:
item: The item to maybe await.
Returns:
The item, or the return once the item was awaited.
"""
if inspect.iscoroutine(item):
return await item
return t.cast("T", item)
ChoiceT = t.TypeVar("ChoiceT", str, int, float)
# fmt: off
if sys.version_info >= (3, 11):
StrEnum = enum.StrEnum
"""
Alias to ``enum.StrEnum`` in Python versions 3.11 or later, otherwise a
custom enum base class for compatibility.
See Also:
:func:`~to_choices`
"""
else:
[docs]
class StrEnum(str, enum.Enum):
"""
Alias to ``enum.StrEnum`` in Python versions 3.11 or later, otherwise a
custom enum base class for compatibility.
See Also:
:func:`~to_choices`
"""
[docs]
class FloatEnum(float, enum.Enum): # noqa: E302
"""
Custom enum base class for enums with float values.
See Also:
:func:`~to_choices`
"""
# fmt: on
@t.overload
def to_choices(raw: Sequence[ChoiceT], localize: bool = False) -> Sequence[options.Choice[ChoiceT]]: ...
@t.overload
def to_choices(raw: Sequence[tuple[str, ChoiceT]], localize: bool = False) -> Sequence[options.Choice[ChoiceT]]: ...
@t.overload
def to_choices(raw: type[StrEnum], localize: bool = False) -> Sequence[options.Choice[str]]: ...
@t.overload
def to_choices(raw: type[enum.IntEnum], localize: bool = False) -> Sequence[options.Choice[int]]: ...
@t.overload
def to_choices(raw: type[FloatEnum], localize: bool = False) -> Sequence[options.Choice[float]]: ...
[docs]
def to_choices(
raw: Sequence[t.Any] | Sequence[tuple[str, t.Any]] | type[StrEnum] | type[enum.IntEnum] | type[FloatEnum],
localize: bool = False,
) -> Sequence[options.Choice[t.Any]]:
"""
Convert various values to :obj:`~lightbulb.commands.options.Choice` objects for use within command options.
When using enums to define choices, you should use the enum types exported by this module where available
(:obj:`~StrEnum`, :obj:`~FloatEnum`). This ensures that your type checker doesn't complain. If you are using
Python 3.11 or newer, you may use :obj:`enum.StrEnum` as the one provided by this module is just an alias. For
integer enums you should always use the :obj:`enum.IntEnum` class.
Args:
raw: The values to convert to :obj:`~lightbulb.commands.options.Choice` objects.
localize: Whether the name of the choice should be interpreted as a localization key.
Returns:
A sequence of :obj:`~lightbulb.commands.options.Choice` objects.
Example:
Using sequences as the input:
.. code-block:: python
>>> lightbulb.utils.to_choices(["foo", "bar", "baz"])
[Choice("foo", "foo"), Choice("bar", "bar"), Choice("baz", "baz)]
>>> lightbulb.utils.to_choices([1, 2, 3])
[Choice("1", 1), Choice("2", 2), Choice("3", 3)]
>>> lightbulb.utils.to_choices([1.5, 2.5, 3.5])
[Choice("1", 1.5), Choice("2", 2.5), Choice("3", 3.5)]
>>> lightbulb.utils.to_choices([("foo", "val1"), ("bar", "val2"), ("baz", "val3")])
[Choice("foo", "val1"), Choice("bar", "val2"), Choice("baz", "val3")]
>>> lightbulb.utils.to_choices([("foo", 1), ("bar", 2), ("baz", 3)])
[Choice("foo", 1), Choice("bar", 2), Choice("baz", 3)]
>>> lightbulb.utils.to_choices([("foo", 1.5), ("bar", 2.5), ("baz", 3.5)])
[Choice("foo", 1.5), Choice("bar", 2.5), Choice("baz", 3.5)]
Using enums as the input:
.. code-block:: python
>>> class StrChoices(lightbulb.utils.StrEnum):
... FOO = "foo"
... BAR = "bar"
... BAZ = "baz"
>>> lightbulb.utils.to_choices(StrChoices)
[Choice("FOO", "foo"), Choice("BAR", "bar"), Choice("BAZ", "baz")]
>>> class IntChoices(enum.IntEnum):
... FOO = 1
... BAR = 2
... BAZ = 3
>>> lightbulb.utils.to_choices(IntChoices)
[Choice("FOO", 1), Choice("BAR", 2), Choice("BAZ", 3)]
>>> class FloatChoices(lightbulb.utils.FloatEnum):
... FOO = 1.5
... BAR = 2.5
... BAZ = 3.5
[Choice("FOO", 1.5), Choice("BAR", 2.5), Choice("BAZ", 3.5)]
.. versionadded:: 3.1.2
"""
from lightbulb.commands.options import Choice
if isinstance(raw, Sequence):
if not raw:
return []
if isinstance(raw[0], tuple):
return [Choice(tup[0], tup[1], localize) for tup in t.cast("Sequence[tuple[str, t.Any]]", raw)]
return [Choice(str(elem), elem, localize) for elem in t.cast("Sequence[t.Any]", raw)]
# noinspection PyUnreachableCode
return [Choice(value.name, value.value, localize) for value in raw]