Skip to content

Commit

Permalink
Further improve typing
Browse files Browse the repository at this point in the history
Co-authored-by: davfsa <[email protected]>
  • Loading branch information
hypergonial and davfsa committed Dec 10, 2023
1 parent b1fec52 commit 7aa7d58
Show file tree
Hide file tree
Showing 12 changed files with 69 additions and 144 deletions.
7 changes: 1 addition & 6 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
]
extensions = ["sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.viewcode"]

autodoc_default_options = {"member-order": "groupwise"}

Expand Down
8 changes: 1 addition & 7 deletions miru/abc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
from .item import *
from .item_handler import *

__all__ = (
"Item",
"ItemHandler",
"ViewItem",
"ModalItem",
"DecoratedItem",
)
__all__ = ("Item", "ItemHandler", "ViewItem", "ModalItem", "DecoratedItem")

# MIT License
#
Expand Down
2 changes: 1 addition & 1 deletion miru/abc/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(
self._is_persistent: bool = bool(custom_id)
"""If True, the custom_id was provided by the user, and not randomly generated."""

self._handler: t.Optional[ItemHandler[BuilderT]] = None
self._handler: t.Optional[ItemHandler[BuilderT, t.Any, t.Any]] = None
"""The handler the item was added to, if any."""

@property
Expand Down
29 changes: 16 additions & 13 deletions miru/abc/item_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@
from .item import Item

if t.TYPE_CHECKING:
import typing_extensions as te

from ..context import Context
from ..events import EventHandler

__all__ = ("ItemHandler",)


BuilderT = t.TypeVar("BuilderT", bound=hikari.api.ComponentBuilder)
ContextT = t.TypeVar("ContextT", bound="Context[t.Any]")
ItemT = t.TypeVar("ItemT", bound="Item[t.Any]")


class _Weights:
class _Weights(t.Generic[ItemT]):
"""
Calculate the position of an item based on it's width, and keep track of item positions
"""
Expand All @@ -34,7 +38,7 @@ class _Weights:
def __init__(self) -> None:
self._weights = [0, 0, 0, 0, 0]

def add_item(self, item: Item[BuilderT]) -> None:
def add_item(self, item: ItemT) -> None:
if item.row is not None:
if item.width + self._weights[item.row] > 5:
raise RowFullError(f"Item does not fit on row {item.row}!")
Expand All @@ -49,7 +53,7 @@ def add_item(self, item: Item[BuilderT]) -> None:
return
raise HandlerFullError("Item does not fit on this item handler.")

def remove_item(self, item: Item[BuilderT]) -> None:
def remove_item(self, item: ItemT) -> None:
if item._rendered_row is not None:
self._weights[item._rendered_row] -= item.width
item._rendered_row = None
Expand All @@ -58,8 +62,7 @@ def clear(self) -> None:
self._weights = [0, 0, 0, 0, 0]


# Add Sequence[hikari.api.MessageActionRowBuilder] here when dropping 3.8 support
class ItemHandler(Sequence, abc.ABC, t.Generic[BuilderT]): # type: ignore[type-arg]
class ItemHandler(Sequence[BuilderT], abc.ABC, t.Generic[BuilderT, ContextT, ItemT]):
"""Abstract base class all item-handlers (e.g. views, modals) inherit from.
Parameters
Expand All @@ -85,13 +88,13 @@ def __init__(self, *, timeout: t.Optional[t.Union[float, int, datetime.timedelta
timeout = timeout.total_seconds()

self._timeout: t.Optional[float] = float(timeout) if timeout else None
self._children: t.List[Item[BuilderT]] = []
self._children: t.List[ItemT] = []

self._weights: _Weights = _Weights()
self._weights: _Weights[ItemT] = _Weights()
self._stopped: asyncio.Event = asyncio.Event()
self._timeout_task: t.Optional[asyncio.Task[None]] = None
self._running_tasks: t.MutableSequence[asyncio.Task[t.Any]] = []
self._last_context: t.Optional[Context[t.Any]] = None
self._last_context: t.Optional[ContextT] = None

if len(self.children) > 25:
raise HandlerFullError(f"{type(self).__name__} cannot have more than 25 components attached.")
Expand Down Expand Up @@ -124,7 +127,7 @@ def __reversed__(self) -> t.Iterator[BuilderT]:
return self.build().__reversed__()

@property
def children(self) -> t.Sequence[Item[BuilderT]]:
def children(self) -> t.Sequence[ItemT]:
"""
A list of all items attached to the item handler.
"""
Expand Down Expand Up @@ -155,7 +158,7 @@ def bot(self) -> MiruAware:
return self.app

@property
def last_context(self) -> t.Optional[Context[t.Any]]:
def last_context(self) -> t.Optional[ContextT]:
"""
The last context that was received by the item handler.
"""
Expand All @@ -166,7 +169,7 @@ def last_context(self) -> t.Optional[Context[t.Any]]:
def _builder(self) -> t.Type[BuilderT]:
...

def add_item(self, item: Item[BuilderT]) -> ItemHandler[BuilderT]:
def add_item(self, item: ItemT) -> te.Self:
"""Adds a new item to the item handler.
Parameters
Expand Down Expand Up @@ -212,7 +215,7 @@ def add_item(self, item: Item[BuilderT]) -> ItemHandler[BuilderT]:

return self

def remove_item(self, item: Item[BuilderT]) -> ItemHandler[BuilderT]:
def remove_item(self, item: ItemT) -> te.Self:
"""Removes the specified item from the item handler.
Parameters
Expand All @@ -235,7 +238,7 @@ def remove_item(self, item: Item[BuilderT]) -> ItemHandler[BuilderT]:

return self

def clear_items(self) -> ItemHandler[BuilderT]:
def clear_items(self) -> te.Self:
"""Removes all items from this item handler.
Returns
Expand Down
10 changes: 1 addition & 9 deletions miru/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,7 @@ def button(
def decorator(func: t.Callable[[ViewT, Button, ViewContextT], t.Awaitable[None]]) -> DecoratedItem:
if not inspect.iscoroutinefunction(func):
raise TypeError("button must decorate coroutine function.")
item = Button(
label=label,
custom_id=custom_id,
style=style,
emoji=emoji,
row=row,
disabled=disabled,
url=None,
)
item = Button(label=label, custom_id=custom_id, style=style, emoji=emoji, row=row, disabled=disabled, url=None)

return DecoratedItem(item, func)

Expand Down
4 changes: 1 addition & 3 deletions miru/context/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ def values(self) -> t.Mapping[ModalItem, str]:
return self._values

def get_value_by_predicate(
self,
predicate: t.Callable[[ModalItem], bool],
default: hikari.UndefinedOr[T] = hikari.UNDEFINED,
self, predicate: t.Callable[[ModalItem], bool], default: hikari.UndefinedOr[T] = hikari.UNDEFINED
) -> T | str:
"""Get the value for the first modal item that matches the given predicate.
Expand Down
14 changes: 5 additions & 9 deletions miru/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@
if t.TYPE_CHECKING:
from .traits import MiruAware

__all__ = (
"Event",
"ComponentInteractionCreateEvent",
"ModalInteractionCreateEvent",
)
__all__ = ("Event", "ComponentInteractionCreateEvent", "ModalInteractionCreateEvent")


@attr.define()
Expand Down Expand Up @@ -105,10 +101,10 @@ class EventHandler:
_app: t.Optional[MiruAware] = None
"""The currently running app instance that will be subscribed to the listener."""

_bound_handlers: t.MutableMapping[hikari.Snowflakeish, ItemHandler[t.Any]] = {}
_bound_handlers: t.MutableMapping[hikari.Snowflakeish, ItemHandler[t.Any, t.Any, t.Any]] = {}
"""A mapping of message_id to ItemHandler. This contains handlers that are bound to a message or custom_id."""

_handlers: t.MutableMapping[str, ItemHandler[t.Any]] = {}
_handlers: t.MutableMapping[str, ItemHandler[t.Any, t.Any, t.Any]] = {}
"""A mapping of custom_id to ItemHandler. This only contains handlers that are not bound to a message."""

def __new__(cls: t.Type[EventHandler]) -> EventHandler:
Expand All @@ -132,7 +128,7 @@ def close(self) -> None:
self._handlers.clear()
self._app = None

def add_handler(self, handler: ItemHandler[t.Any]) -> None:
def add_handler(self, handler: ItemHandler[t.Any, t.Any, t.Any]) -> None:
"""Add a handler to the event handler."""
if isinstance(handler, View):
if handler.is_bound and handler._message_id is not None:
Expand All @@ -143,7 +139,7 @@ def add_handler(self, handler: ItemHandler[t.Any]) -> None:
elif isinstance(handler, Modal):
self._handlers[handler.custom_id] = handler

def remove_handler(self, handler: ItemHandler[t.Any]) -> None:
def remove_handler(self, handler: ItemHandler[t.Any, t.Any, t.Any]) -> None:
"""Remove a handler from the event handler."""
if isinstance(handler, View):
if handler.is_bound and handler._message_id is not None:
Expand Down
20 changes: 7 additions & 13 deletions miru/ext/nav/navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

from .items import FirstButton, IndicatorButton, LastButton, NavButton, NavItem, NextButton, PrevButton

if t.TYPE_CHECKING:
import typing_extensions as te

logger = logging.getLogger(__name__)

__all__ = ("NavigatorView", "Page")
Expand Down Expand Up @@ -120,7 +123,7 @@ def get_default_buttons(self) -> t.Sequence[NavButton]:
"""
return [FirstButton(), PrevButton(), IndicatorButton(), NextButton(), LastButton()]

def add_item(self, item: Item[hikari.impl.MessageActionRowBuilder]) -> NavigatorView:
def add_item(self, item: Item[hikari.impl.MessageActionRowBuilder]) -> te.Self:
"""Adds a new item to the navigator. Item must be of type NavItem.
Parameters
Expand All @@ -141,21 +144,15 @@ def add_item(self, item: Item[hikari.impl.MessageActionRowBuilder]) -> Navigator
if not isinstance(item, NavItem):
raise TypeError(f"Expected type 'NavItem' for parameter item, not '{type(item).__name__}'.")

return t.cast(NavigatorView, super().add_item(item))

def remove_item(self, item: Item[hikari.impl.MessageActionRowBuilder]) -> NavigatorView:
return t.cast(NavigatorView, super().remove_item(item))

def clear_items(self) -> NavigatorView:
return t.cast(NavigatorView, super().clear_items())
return super().add_item(item)

def _get_page_payload(
self, page: t.Union[str, hikari.Embed, t.Sequence[hikari.Embed], Page]
) -> t.MutableMapping[str, t.Any]:
"""Get the page content that is to be sent."""

if isinstance(page, Page):
d = page._build_payload()
d: t.Dict[str, t.Any] = page._build_payload()
d["components"] = self
if self.ephemeral:
d["flags"] = hikari.MessageFlag.EPHEMERAL
Expand Down Expand Up @@ -310,10 +307,7 @@ async def send(
else:
self._inter = to
if not responded:
await to.create_initial_response(
hikari.ResponseType.MESSAGE_CREATE,
**payload,
)
await to.create_initial_response(hikari.ResponseType.MESSAGE_CREATE, **payload)
message = await to.fetch_initial_response()
else:
message = await to.execute(**payload)
Expand Down
36 changes: 9 additions & 27 deletions miru/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@

from miru.exceptions import BootstrapFailureError, HandlerFullError

from .abc.item import Item, ModalItem
from .abc.item import ModalItem
from .abc.item_handler import ItemHandler
from .context.modal import ModalContext

if t.TYPE_CHECKING:
import typing_extensions as te

ModalContextT = t.TypeVar("ModalContextT", bound=ModalContext)

__all__ = ("Modal",)


class Modal(ItemHandler[hikari.impl.ModalActionRowBuilder]):
class Modal(ItemHandler[hikari.impl.ModalActionRowBuilder, ModalContext, ModalItem]):
"""Represents a Discord Modal.
Parameters
Expand Down Expand Up @@ -123,27 +126,16 @@ def values(self) -> t.Optional[t.Mapping[ModalItem, str]]:
"""
return self._values

@property
def last_context(self) -> t.Optional[ModalContextT]:
"""
Context proxying the last interaction that was received by the modal.
"""
return t.cast(ModalContextT, self._last_context)

@property
def _builder(self) -> t.Type[hikari.impl.ModalActionRowBuilder]:
return hikari.impl.ModalActionRowBuilder

@property
def children(self) -> t.Sequence[ModalItem]:
return t.cast(t.Sequence[ModalItem], super().children)

def add_item(self, item: Item[hikari.impl.ModalActionRowBuilder]) -> Modal:
def add_item(self, item: ModalItem) -> te.Self:
"""Adds a new item to the modal.
Parameters
----------
item : Item
item : ModalItem
An instance of ModalItem to be added.
Raises
Expand All @@ -167,13 +159,7 @@ def add_item(self, item: Item[hikari.impl.ModalActionRowBuilder]) -> Modal:
if not isinstance(item, ModalItem):
raise TypeError(f"Expected type ModalItem for parameter item, not {type(item).__name__}.")

return t.cast(Modal, super().add_item(item))

def remove_item(self, item: Item[hikari.impl.ModalActionRowBuilder]) -> Modal:
return t.cast(Modal, super().remove_item(item))

def clear_items(self) -> Modal:
return t.cast(Modal, super().clear_items())
return super().add_item(item)

async def modal_check(self, context: ModalContextT) -> bool:
"""Called before any callback in the modal is called. Must evaluate to a truthy value to pass.
Expand All @@ -191,11 +177,7 @@ async def modal_check(self, context: ModalContextT) -> bool:
"""
return True

async def on_error(
self,
error: Exception,
context: t.Optional[ModalContextT] = None,
) -> None:
async def on_error(self, error: Exception, context: t.Optional[ModalContextT] = None) -> None:
"""Called when an error occurs in a callback function.
Override for custom error-handling logic.
Expand Down
Loading

0 comments on commit 7aa7d58

Please sign in to comment.