Skip to content

Commit

Permalink
Fix mypy, improve slotting, swap dataclasses for attrs-defined classes
Browse files Browse the repository at this point in the history
  • Loading branch information
null-domain authored Oct 2, 2023
1 parent b8c3bb5 commit 105f704
Show file tree
Hide file tree
Showing 9 changed files with 43 additions and 33 deletions.
2 changes: 2 additions & 0 deletions lightbulb/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@


class _ExclusiveCheck:
__slots__ = ("_checks",)

def __init__(self, *checks: "Check") -> None:
self._checks = list(checks)

Expand Down
26 changes: 12 additions & 14 deletions lightbulb/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import abc
import asyncio
import collections
import dataclasses
import datetime
import enum
import inspect
import re
import typing as t

import attrs
import hikari

from lightbulb import errors
Expand Down Expand Up @@ -103,8 +103,6 @@ def recreate_subcommands(self, raw_cmds: t.Sequence[CommandLike], app: app_.BotA


class _SubcommandListProxy(collections.UserList): # type: ignore
__slots__ = ("parents",)

def __init__(self, *args: t.Any, parent: _HasRecreateSubcommands, **kwargs: t.Any) -> None:
super().__init__(*args, **kwargs)
self.parents = [parent]
Expand Down Expand Up @@ -136,7 +134,7 @@ class OptionModifier(enum.Enum):
"""Consume rest option. This will consume the entire remainder of the string."""


@dataclasses.dataclass
@attrs.define(slots=True)
class OptionLike:
"""
Generic dataclass representing a command option. Compatible with both prefix and application commands.
Expand Down Expand Up @@ -184,13 +182,13 @@ class OptionLike:
"""
autocomplete: bool = False
"""Whether the option should be autocompleted or not. This only affects slash commands."""
name_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = dataclasses.field(default_factory=dict)
name_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = attrs.field(factory=dict)
"""
A mapping of locale to name localizations for this option
.. versionadded:: 2.3.0
"""
description_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = dataclasses.field(default_factory=dict)
description_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = attrs.field(factory=dict)
"""
A mapping of locale to description localizations for this option
Expand Down Expand Up @@ -274,7 +272,7 @@ def as_application_command_option(self) -> hikari.CommandOption:
return hikari.CommandOption(**kwargs)


@dataclasses.dataclass
@attrs.define(slots=True)
class CommandLike:
"""Generic dataclass representing a command. This can be converted into any command object."""

Expand All @@ -284,19 +282,19 @@ class CommandLike:
"""The name of the command."""
description: str
"""The description of the command."""
options: t.MutableMapping[str, OptionLike] = dataclasses.field(default_factory=dict)
options: t.MutableMapping[str, OptionLike] = attrs.field(factory=dict)
"""The options for the command."""
checks: t.Sequence[t.Union[checks.Check, checks._ExclusiveCheck]] = dataclasses.field(default_factory=list)
checks: t.Sequence[t.Union[checks.Check, checks._ExclusiveCheck]] = attrs.field(factory=list)
"""The checks for the command."""
error_handler: t.Optional[
t.Callable[[events.CommandErrorEvent], t.Coroutine[t.Any, t.Any, t.Optional[bool]]]
] = None
"""The error handler for the command."""
aliases: t.Sequence[str] = dataclasses.field(default_factory=list)
aliases: t.Sequence[str] = attrs.field(factory=list)
"""The aliases for the command. This only affects prefix commands."""
guilds: hikari.UndefinedOr[t.Sequence[int]] = hikari.UNDEFINED
"""The guilds for the command. This only affects application commands."""
subcommands: t.List[CommandLike] = dataclasses.field(default_factory=list)
subcommands: t.List[CommandLike] = attrs.field(factory=list)
"""Subcommands for the command."""
parser: t.Optional[t.Type[parser_.BaseParser]] = None
"""The argument parser to use for prefix commands."""
Expand Down Expand Up @@ -344,13 +342,13 @@ class CommandLike:
.. versionadded:: 2.2.3
"""
name_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = dataclasses.field(default_factory=dict)
name_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = attrs.field(factory=dict)
"""
A mapping of locale to name localizations for this command
.. versionadded:: 2.3.0
"""
description_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = dataclasses.field(default_factory=dict)
description_localizations: t.Mapping[t.Union[hikari.Locale, str], str] = attrs.field(factory=dict)
"""
A mapping of locale to description localizations for this command
Expand Down Expand Up @@ -379,7 +377,7 @@ class CommandLike:
],
],
],
] = dataclasses.field(default_factory=dict, init=False)
] = attrs.field(factory=dict, init=False)

async def __call__(self, context: context_.base.Context) -> None:
await self.callback(context)
Expand Down
2 changes: 1 addition & 1 deletion lightbulb/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def decorate(c_like: commands.base.CommandLike) -> commands.base.CommandLike:
cmd_doc = inspect.getdoc(c_like.callback)
if cmd_doc is None:
raise ValueError("docstring=True was provided but the command does not have a docstring")
getter = lambda _, __: cmd_doc # type: ignore
getter = lambda _, __: cmd_doc
else:
assert text is not None
getter = text
Expand Down
16 changes: 0 additions & 16 deletions lightbulb/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ class CommandNotFound(LightbulbError):
is not found. This will only be raised for prefix commands.
"""

__slots__ = ("invoked_with",)

def __init__(self, *args: t.Any, invoked_with: str) -> None:
super().__init__(*args)
self.invoked_with: str = invoked_with
Expand All @@ -125,8 +123,6 @@ class CommandInvocationError(LightbulbError):
``CommandInvocationError.__cause__`` or ``CommandInvocationError.original``.
"""

__slots__ = ("original",)

def __init__(self, *args: t.Any, original: Exception) -> None:
super().__init__(*args)
self.original: Exception = original
Expand All @@ -139,8 +135,6 @@ class CommandIsOnCooldown(LightbulbError):
Error raised when a command was on cooldown when it was attempted to be invoked.
"""

__slots__ = ("retry_after",)

def __init__(self, *args: t.Any, retry_after: float) -> None:
super().__init__(*args)
self.retry_after: float = retry_after
Expand All @@ -152,8 +146,6 @@ class ConverterFailure(LightbulbError):
Error raised when option type conversion fails while prefix command arguments are being parsed.
"""

__slots__ = ("option", "raw_value")

def __init__(self, *args: t.Any, opt: commands.base.OptionLike, raw: str) -> None:
super().__init__(*args)
self.option: commands.base.OptionLike = opt
Expand All @@ -171,8 +163,6 @@ class NotEnoughArguments(LightbulbError):
Error raised when a prefix command expects more options than could be parsed from the user's input.
"""

__slots__ = ("missing_options",)

def __init__(self, *args: t.Any, missing: t.Sequence[commands.base.OptionLike]) -> None:
super().__init__(*args)
self.missing_options: t.Sequence[commands.base.OptionLike] = missing
Expand All @@ -184,8 +174,6 @@ class MissingRequiredAttachmentArgument(LightbulbError):
Error raised when a prefix command expects an attachment but none were supplied with the invocation.
"""

__slots__ = ("missing_option",)

def __init__(self, *args: t.Any, missing: commands.base.OptionLike) -> None:
super().__init__(*args)
self.missing_option: commands.base.OptionLike = missing
Expand All @@ -198,8 +186,6 @@ class MaxConcurrencyLimitReached(LightbulbError):
has been exceeded.
"""

__slots__ = ("bucket",)

def __init__(self, *args: t.Any, bucket: t.Type[buckets.Bucket]) -> None:
super().__init__(*args)
self.bucket = bucket
Expand All @@ -217,8 +203,6 @@ class CheckFailure(LightbulbError):
multiple checks failing, via ``CheckFailure.causes`` (since version `2.2.1`).
"""

__slots__ = ("causes",)

def __init__(self, *args: t.Any, causes: t.Optional[t.Sequence[Exception]] = None) -> None:
super().__init__(*args)
self.causes: t.Sequence[Exception] = causes or []
Expand Down
2 changes: 2 additions & 0 deletions lightbulb/help_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ class DefaultHelpCommand(BaseHelpCommand):
An implementation of the :obj:`~BaseHelpCommand` that the bot uses by default.
"""

__slots__ = ()

@staticmethod
async def _get_command_plugin_map(
cmd_map: t.Mapping[str, commands.base.Command], context: context_.base.Context
Expand Down
4 changes: 2 additions & 2 deletions lightbulb/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def _serialise_option(option: hikari.CommandOption) -> t.Dict[str, t.Any]:
"choices": list(
sorted(
[{"n": c.name, "v": c.value} for c in option.choices] if option.choices is not None else [],
key=lambda d: d["n"], # type: ignore
key=lambda d: d["n"],
)
),
"options": [_serialise_option(o) for o in option.options] if option.options is not None else [],
Expand Down Expand Up @@ -92,7 +92,7 @@ def _serialise_lightbulb_command(command: base.ApplicationCommand) -> t.Dict[str
"type": create_kwargs["type"],
"name": create_kwargs["name"],
"description": create_kwargs.get("description"),
"options": [_serialise_option(o) for o in sorted(create_kwargs.get("options", []), key=lambda o: o.name)], # type: ignore
"options": [_serialise_option(o) for o in sorted(create_kwargs.get("options", []), key=lambda o: o.name)],
"guild_id": _GuildIDCollection(command.guilds) if command.guilds else None,
"default_member_permissions": command.app_command_default_member_permissions,
"dm_enabled": command.app_command_dm_enabled if not command.guilds else False,
Expand Down
2 changes: 2 additions & 0 deletions lightbulb/utils/data_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class DataStore(t.Dict[str, t.Any]):
DataStore()
"""

__slots__ = ()

def __repr__(self) -> str:
return "DataStore(" + ", ".join(f"{k}={v!r}" for k, v in self.items()) + ")"

Expand Down
4 changes: 4 additions & 0 deletions lightbulb/utils/nav.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ async def foo(ctx):
await navigator.run(ctx)
"""

__slots__ = ("pages", "buttons", "_timeout", "current_page_index", "_context", "_msg", "_timeout_task")

def __init__(
self,
pages: t.Union[t.Iterable[T], t.Iterator[T]],
Expand Down Expand Up @@ -384,6 +386,8 @@ async def foo(ctx):
await navigator.run(ctx)
"""

__slots__ = ("pages", "buttons", "_timeout", "current_page_index", "_context", "_msg", "_timeout_task")

def __init__(
self,
pages: t.Union[t.Iterable[T], t.Iterator[T]],
Expand Down
18 changes: 18 additions & 0 deletions lightbulb/utils/pag.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@


class Paginator(abc.ABC, t.Generic[T]):
__slots__ = (
"_max_total_chars",
"_max_total_lines",
"_max_content_chars",
"_max_content_lines",
"_line_separator",
"_page_prefix",
"_page_suffix",
"_next_page",
"_pages",
"_page_factory",
"current_page",
)

@abc.abstractmethod
def __init__(
self,
Expand Down Expand Up @@ -210,6 +224,8 @@ async def guilds(ctx):
await ctx.respond(page)
"""

__slots__ = ()

def __init__(
self,
*,
Expand Down Expand Up @@ -245,6 +261,8 @@ class EmbedPaginator(Paginator[hikari.Embed]):
suffix (:obj:`str`): The string to suffix every page with. Defaults to an empty string.
"""

__slots__ = ()

def __init__(
self,
*,
Expand Down

0 comments on commit 105f704

Please sign in to comment.