diff --git a/src/ductile/controller/controller.py b/src/ductile/controller/controller.py index 5ab9a25..c7a2b6a 100644 --- a/src/ductile/controller/controller.py +++ b/src/ductile/controller/controller.py @@ -1,12 +1,13 @@ -from collections.abc import Generator from typing import TYPE_CHECKING, Any, Literal, NamedTuple, overload -import discord - from ..internal import _InternalView # noqa: TID252 from ..state import State # noqa: TID252 if TYPE_CHECKING: + from collections.abc import Generator + + from discord import Message + from ..view import View, ViewObject # noqa: TID252 from .type import ViewObjectDictWithAttachment, ViewObjectDictWithFiles @@ -21,10 +22,10 @@ def __init__(self, view: "View", *, timeout: float | None = 180) -> None: self.__view = view view._controller = self # noqa: SLF001 self.__raw_view = _InternalView(timeout=timeout, on_error=self.__view.on_error, on_timeout=self.__view.on_timeout) - self.__message: discord.Message | None = None + self.__message: "Message | None" = None @property - def message(self) -> discord.Message | None: + def message(self) -> "Message | None": """ return attached message with the View. @@ -36,7 +37,7 @@ def message(self) -> discord.Message | None: return self.__message @message.setter - def message(self, value: discord.Message | None) -> None: + def message(self, value: "Message | None") -> None: self.__message = value async def send(self) -> None: @@ -80,7 +81,7 @@ async def wait(self) -> ViewResult: d[key] = state.get_state() return ViewResult(timed_out, d) - def _get_all_state_in_view(self) -> Generator[tuple[str, State[Any]], None, None]: + def _get_all_state_in_view(self) -> "Generator[tuple[str, State[Any]], None, None]": for k, v in self.__view.__dict__.items(): if isinstance(v, State): yield k, v @@ -119,7 +120,7 @@ def _process_view_for_discord( view_object: "ViewObject" = self.__view.render() if mode == "attachment": - d_attachment: ViewObjectDictWithAttachment = {} + d_attachment: "ViewObjectDictWithAttachment" = {} d_attachment["content"] = view_object.content if view_object.embeds: d_attachment["embeds"] = view_object.embeds @@ -133,7 +134,7 @@ def _process_view_for_discord( d_attachment["view"] = v return d_attachment - d_file: ViewObjectDictWithFiles = {} + d_file: "ViewObjectDictWithFiles" = {} d_file["content"] = view_object.content if view_object.embeds: d_file["embeds"] = view_object.embeds diff --git a/src/ductile/controller/interaction_controller.py b/src/ductile/controller/interaction_controller.py index bd022d3..1171cf8 100644 --- a/src/ductile/controller/interaction_controller.py +++ b/src/ductile/controller/interaction_controller.py @@ -1,10 +1,12 @@ from typing import TYPE_CHECKING -import discord +from discord import CategoryChannel, ForumChannel from .controller import ViewController if TYPE_CHECKING: + from discord import Interaction + from ..view import View # noqa: TID252 @@ -13,7 +15,7 @@ def __init__( self, view: "View", *, - interaction: discord.Interaction, + interaction: "Interaction", timeout: float | None = 180, ephemeral: bool = False, ) -> None: @@ -26,7 +28,7 @@ async def send(self) -> None: view_kwargs = self._process_view_for_discord("files") if target.is_expired(): - if target.channel is not None and not isinstance(target.channel, discord.CategoryChannel | discord.ForumChannel): + if target.channel is not None and not isinstance(target.channel, CategoryChannel | ForumChannel): self.message = await target.channel.send(**view_kwargs) return diff --git a/src/ductile/controller/messageable_controller.py b/src/ductile/controller/messageable_controller.py index 16b8c43..2ea2858 100644 --- a/src/ductile/controller/messageable_controller.py +++ b/src/ductile/controller/messageable_controller.py @@ -1,15 +1,15 @@ from typing import TYPE_CHECKING -import discord - from .controller import ViewController if TYPE_CHECKING: + import discord + from ..view import View # noqa: TID252 class MessageableController(ViewController): - def __init__(self, view: "View", *, messageable: discord.abc.Messageable, timeout: float | None = 180) -> None: + def __init__(self, view: "View", *, messageable: "discord.abc.Messageable", timeout: float | None = 180) -> None: super().__init__(view, timeout=timeout) self.__messageable = messageable diff --git a/src/ductile/controller/type.py b/src/ductile/controller/type.py index ed415d8..84e2950 100644 --- a/src/ductile/controller/type.py +++ b/src/ductile/controller/type.py @@ -1,6 +1,7 @@ -from typing import TypedDict +from typing import TYPE_CHECKING, TypedDict -import discord +if TYPE_CHECKING: + from discord import Embed, File, ui class _ViewObjectDict(TypedDict, total=False): @@ -22,13 +23,13 @@ class _ViewObjectDict(TypedDict, total=False): """ content: str - embeds: list[discord.Embed] - view: discord.ui.View + embeds: "list[Embed]" + view: "ui.View" class ViewObjectDictWithAttachment(_ViewObjectDict, total=False): - attachments: list[discord.File] + attachments: "list[File]" class ViewObjectDictWithFiles(_ViewObjectDict, total=False): - files: list[discord.File] + files: "list[File]" diff --git a/src/ductile/internal/view.py b/src/ductile/internal/view.py index 47fe9ca..be21316 100644 --- a/src/ductile/internal/view.py +++ b/src/ductile/internal/view.py @@ -1,10 +1,10 @@ from typing import TYPE_CHECKING from discord import ui -from discord.interactions import Interaction -from discord.ui.item import Item if TYPE_CHECKING: + from discord import Interaction + from ..types import ViewErrorHandler, ViewTimeoutHandler # noqa: TID252 __all__ = [ @@ -24,7 +24,7 @@ def __init__( self.__on_error = on_error self.__on_timeout = on_timeout - async def on_error(self, interaction: Interaction, error: Exception, item: Item) -> None: + async def on_error(self, interaction: "Interaction", error: Exception, item: ui.Item) -> None: if self.__on_error: await self.__on_error(interaction, error, item) diff --git a/src/ductile/ui/button.py b/src/ductile/ui/button.py index e99a825..02427d9 100644 --- a/src/ductile/ui/button.py +++ b/src/ductile/ui/button.py @@ -1,12 +1,13 @@ from typing import TYPE_CHECKING, Literal, TypedDict -import discord -from discord import Emoji, PartialEmoji, ui -from discord.interactions import Interaction +from discord import ButtonStyle as _ButtonStyle +from discord import ui from ..utils import call_any_function # noqa: TID252 if TYPE_CHECKING: + from discord import Emoji, Interaction, PartialEmoji + from ..types import InteractionCallback # noqa: TID252 @@ -16,7 +17,7 @@ class _ButtonStyleRequired(TypedDict): class ButtonStyle(_ButtonStyleRequired, total=False): disabled: bool - emoji: str | Emoji | PartialEmoji | None + emoji: "str | Emoji | PartialEmoji | None" row: Literal[0, 1, 2, 3, 4] @@ -30,7 +31,7 @@ def __init__( custom_id: str | None = None, on_click: "InteractionCallback | None" = None, ) -> None: - __style = discord.ButtonStyle[style.get("color", "grey")] + __style = _ButtonStyle[style.get("color", "grey")] __disabled = style.get("disabled", False) __emoji = style.get("emoji", None) __row = style.get("row", None) @@ -44,7 +45,7 @@ def __init__( custom_id=custom_id, ) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction) @@ -52,11 +53,11 @@ async def callback(self, interaction: Interaction) -> None: class LinkButton(ui.Button): def __init__(self, label: str | None = None, /, *, url: str, custom_id: str | None = None) -> None: super().__init__( - style=discord.ButtonStyle.link, + style=_ButtonStyle.link, url=url, label=label, custom_id=custom_id, ) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: pass diff --git a/src/ductile/ui/modal.py b/src/ductile/ui/modal.py index 467bab1..594f36b 100644 --- a/src/ductile/ui/modal.py +++ b/src/ductile/ui/modal.py @@ -1,8 +1,10 @@ from typing import TYPE_CHECKING, Literal, TypedDict -from discord import Interaction, TextStyle, ui +from discord import TextStyle, ui if TYPE_CHECKING: + from discord import Interaction + from ..types import ModalCallback # noqa: TID252 @@ -66,6 +68,6 @@ def __init__( # noqa: PLR0913 for _in in self.__inputs: self.add_item(_in) - async def on_submit(self, interaction: Interaction) -> None: + async def on_submit(self, interaction: "Interaction") -> None: if self.__callback_fn: await self.__callback_fn(interaction, {i.label: i.value for i in self.__inputs}) diff --git a/src/ductile/ui/select.py b/src/ductile/ui/select.py index d21f580..2fd7ffb 100644 --- a/src/ductile/ui/select.py +++ b/src/ductile/ui/select.py @@ -1,13 +1,14 @@ from typing import TYPE_CHECKING, Literal, TypedDict -from discord import Emoji, Interaction, PartialEmoji, ui from discord import SelectOption as _SelectOption -from discord.enums import ChannelType -from pydantic import BaseModel, ConfigDict, Field +from discord import ui +from pydantic import BaseModel, Field from ..utils import call_any_function # noqa: TID252 if TYPE_CHECKING: + from discord import ChannelType, Emoji, Interaction, PartialEmoji + from ..types import ( # noqa: TID252 ChannelSelectCallback, MentionableSelectCallback, @@ -27,10 +28,10 @@ class SelectOption(BaseModel): label: str = Field(min_length=1, max_length=100) value: str | None = Field(default=None, min_length=1, max_length=100) description: str | None = Field(default=None, min_length=1, max_length=100) - emoji: str | Emoji | PartialEmoji | None = Field(default=None) + emoji: "str | Emoji | PartialEmoji | None" = Field(default=None) selected_by_default: bool = Field(default=False) - model_config = ConfigDict(arbitrary_types_allowed=True) + model_config = {"arbitrary_types_allowed": True} class SelectConfigBase(TypedDict, total=False): @@ -43,7 +44,7 @@ class SelectConfig(SelectConfigBase): class ChannelSelectConfig(SelectConfigBase): - channel_types: list[ChannelType] + channel_types: "list[ChannelType]" class RoleSelectConfig(SelectConfigBase): @@ -93,7 +94,7 @@ def __init__( # noqa: PLR0913 self.__callback_fn = on_select super().__init__(**__d) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction, self.values) @@ -123,7 +124,7 @@ def __init__( self.__callback_fn = on_select super().__init__(**__d) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction, self.values) @@ -152,7 +153,7 @@ def __init__( self.__callback_fn = on_select super().__init__(**__d) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction, self.values) @@ -181,7 +182,7 @@ def __init__( self.__callback_fn = on_select super().__init__(**__d) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction, self.values) @@ -210,6 +211,6 @@ def __init__( self.__callback_fn = on_select super().__init__(**__d) - async def callback(self, interaction: Interaction) -> None: + async def callback(self, interaction: "Interaction") -> None: if self.__callback_fn: await call_any_function(self.__callback_fn, interaction, self.values) diff --git a/src/ductile/utils/call.py b/src/ductile/utils/call.py index 73b304c..7e22761 100644 --- a/src/ductile/utils/call.py +++ b/src/ductile/utils/call.py @@ -1,6 +1,8 @@ from asyncio import iscoroutinefunction -from collections.abc import Callable -from typing import ParamSpec, TypeVar +from typing import TYPE_CHECKING, ParamSpec, TypeVar + +if TYPE_CHECKING: + from collections.abc import Callable P = ParamSpec("P") R = TypeVar("R") @@ -10,7 +12,7 @@ ] -async def call_any_function(fn: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R: +async def call_any_function(fn: "Callable[P, R]", *args: P.args, **kwargs: P.kwargs) -> R: if iscoroutinefunction(fn): return await fn(*args, **kwargs) return fn(*args, **kwargs) diff --git a/src/ductile/view.py b/src/ductile/view.py index fe5579c..9fa49d2 100644 --- a/src/ductile/view.py +++ b/src/ductile/view.py @@ -1,14 +1,13 @@ import asyncio from typing import TYPE_CHECKING -import discord -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, Field from .utils import get_logger -# from components.ui.state import State - if TYPE_CHECKING: + from discord import Embed, File, Interaction, ui + from .controller import ViewController @@ -35,11 +34,11 @@ class ViewObject(BaseModel): """ content: str = Field(default="") - embeds: list[discord.Embed] | None = Field(default=None) - files: list[discord.File] | None = Field(default=None) - components: list[discord.ui.Item] | None = Field(default=None) + embeds: "list[Embed] | None" = Field(default=None) + files: "list[File] | None" = Field(default=None) + components: "list[ui.Item] | None" = Field(default=None) - model_config = ConfigDict(arbitrary_types_allowed=True) + model_config = {"arbitrary_types_allowed": True} class View: @@ -102,7 +101,7 @@ def stop(self) -> None: else: self.__logger.warning("Controller is not set") - async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item) -> None: + async def on_error(self, interaction: "Interaction", error: Exception, item: "ui.Item") -> None: """ on_error is called when an error occurs in the view.