hikari-arc 1.2.1__py3-none-any.whl → 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arc/__init__.py +10 -1
- arc/__main__.py +1 -0
- arc/abc/__init__.py +2 -0
- arc/abc/client.py +239 -66
- arc/abc/command.py +15 -12
- arc/abc/error_handler.py +2 -4
- arc/abc/option.py +37 -8
- arc/abc/plugin.py +13 -17
- arc/client.py +1 -2
- arc/command/__init__.py +12 -0
- arc/command/option/__init__.py +7 -0
- arc/command/option/attachment.py +4 -1
- arc/command/option/bool.py +4 -1
- arc/command/option/channel.py +4 -1
- arc/command/option/custom/__init__.py +4 -0
- arc/command/option/custom/color.py +106 -0
- arc/command/option/custom/member.py +98 -0
- arc/command/option/float.py +4 -1
- arc/command/option/int.py +4 -1
- arc/command/option/mentionable.py +4 -1
- arc/command/option/role.py +4 -1
- arc/command/option/str.py +4 -1
- arc/command/option/user.py +4 -1
- arc/command/slash.py +12 -13
- arc/context/base.py +59 -22
- arc/errors.py +18 -0
- arc/extension.py +4 -8
- arc/internal/about.py +1 -1
- arc/internal/options.py +9 -4
- arc/internal/sigparse.py +19 -5
- arc/internal/sync.py +3 -3
- arc/internal/types.py +3 -1
- arc/utils/concurrency_limiter.py +1 -1
- arc/utils/loops.py +44 -22
- arc/utils/ratelimiter.py +1 -1
- {hikari_arc-1.2.1.dist-info → hikari_arc-1.3.1.dist-info}/METADATA +16 -16
- hikari_arc-1.3.1.dist-info/RECORD +59 -0
- {hikari_arc-1.2.1.dist-info → hikari_arc-1.3.1.dist-info}/WHEEL +1 -1
- hikari_arc-1.2.1.dist-info/RECORD +0 -56
- {hikari_arc-1.2.1.dist-info → hikari_arc-1.3.1.dist-info}/LICENSE +0 -0
- {hikari_arc-1.2.1.dist-info → hikari_arc-1.3.1.dist-info}/top_level.txt +0 -0
arc/context/base.py
CHANGED
|
@@ -7,6 +7,7 @@ import logging
|
|
|
7
7
|
import typing as t
|
|
8
8
|
from contextlib import suppress
|
|
9
9
|
|
|
10
|
+
import alluka
|
|
10
11
|
import attr
|
|
11
12
|
import hikari
|
|
12
13
|
|
|
@@ -23,6 +24,9 @@ __all__ = ("Context", "InteractionResponse", "AutodeferMode")
|
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
27
|
+
T = t.TypeVar("T")
|
|
28
|
+
DefaultT = t.TypeVar("DefaultT")
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
@t.final
|
|
28
32
|
class AutodeferMode(enum.IntEnum):
|
|
@@ -254,6 +258,7 @@ class Context(t.Generic[ClientT]):
|
|
|
254
258
|
"_created_at",
|
|
255
259
|
"_has_command_failed",
|
|
256
260
|
"_options",
|
|
261
|
+
"_injection_ctx",
|
|
257
262
|
)
|
|
258
263
|
|
|
259
264
|
def __init__(
|
|
@@ -261,6 +266,7 @@ class Context(t.Generic[ClientT]):
|
|
|
261
266
|
) -> None:
|
|
262
267
|
self._client = client
|
|
263
268
|
self._command = command
|
|
269
|
+
self._injection_ctx = alluka.OverridingContext.from_client(client.injector)
|
|
264
270
|
self._interaction: hikari.CommandInteraction = interaction
|
|
265
271
|
self._options: t.Sequence[hikari.CommandInteractionOption] | None = None
|
|
266
272
|
self._responses: t.MutableSequence[InteractionResponse] = []
|
|
@@ -413,40 +419,39 @@ class Context(t.Generic[ClientT]):
|
|
|
413
419
|
return self._interaction.get_channel()
|
|
414
420
|
|
|
415
421
|
@t.overload
|
|
416
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
417
|
-
|
|
422
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.COLOR]) -> hikari.Color | None: ...
|
|
423
|
+
|
|
424
|
+
@t.overload
|
|
425
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.MEMBER]) -> hikari.Member | None: ...
|
|
418
426
|
|
|
419
427
|
@t.overload
|
|
420
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
421
|
-
...
|
|
428
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.ATTACHMENT]) -> hikari.Attachment | None: ...
|
|
422
429
|
|
|
423
430
|
@t.overload
|
|
424
|
-
def get_option(
|
|
425
|
-
|
|
431
|
+
def get_option(
|
|
432
|
+
self, name: str, opt_type: t.Literal[OptionType.MENTIONABLE]
|
|
433
|
+
) -> hikari.User | hikari.Role | None: ...
|
|
426
434
|
|
|
427
435
|
@t.overload
|
|
428
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
429
|
-
...
|
|
436
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.USER]) -> hikari.User | None: ...
|
|
430
437
|
|
|
431
438
|
@t.overload
|
|
432
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
433
|
-
...
|
|
439
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.ROLE]) -> hikari.Role | None: ...
|
|
434
440
|
|
|
435
441
|
@t.overload
|
|
436
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
437
|
-
...
|
|
442
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.CHANNEL]) -> hikari.PartialChannel | None: ...
|
|
438
443
|
|
|
439
444
|
@t.overload
|
|
440
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
441
|
-
...
|
|
445
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.STRING]) -> str | None: ...
|
|
442
446
|
|
|
443
447
|
@t.overload
|
|
444
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
445
|
-
...
|
|
448
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.BOOLEAN]) -> bool | None: ...
|
|
446
449
|
|
|
447
450
|
@t.overload
|
|
448
|
-
def get_option(self, name: str, opt_type: t.Literal[OptionType.
|
|
449
|
-
|
|
451
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.FLOAT]) -> float | None: ...
|
|
452
|
+
|
|
453
|
+
@t.overload
|
|
454
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.INTEGER]) -> int | None: ...
|
|
450
455
|
|
|
451
456
|
def get_option(self, name: str, opt_type: OptionType) -> t.Any | None:
|
|
452
457
|
"""Get the value of an option by name.
|
|
@@ -530,6 +535,40 @@ class Context(t.Generic[ClientT]):
|
|
|
530
535
|
request = CustomLocaleRequest(self.command, locale, self, key)
|
|
531
536
|
return self._client._custom_locale_provider(request).format(**kwargs)
|
|
532
537
|
|
|
538
|
+
@t.overload
|
|
539
|
+
def get_type_dependency(self, type_: type[T]) -> T: ...
|
|
540
|
+
|
|
541
|
+
@t.overload
|
|
542
|
+
def get_type_dependency(self, type_: type[T], *, default: DefaultT) -> T | DefaultT: ...
|
|
543
|
+
|
|
544
|
+
def get_type_dependency(
|
|
545
|
+
self, type_: type[T], *, default: DefaultT | hikari.UndefinedType = hikari.UNDEFINED
|
|
546
|
+
) -> T | DefaultT:
|
|
547
|
+
"""Get a type dependency for the current context.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
type_ : type[T]
|
|
552
|
+
The type of the dependency.
|
|
553
|
+
default : DefaultT
|
|
554
|
+
The default value to return if the dependency does not exist.
|
|
555
|
+
If not provided, this will raise an exception if the dependency does not exist.
|
|
556
|
+
|
|
557
|
+
Returns
|
|
558
|
+
-------
|
|
559
|
+
T | DefaultT
|
|
560
|
+
The instance of the dependency, if it exists, otherwise `default`.
|
|
561
|
+
|
|
562
|
+
Raises
|
|
563
|
+
------
|
|
564
|
+
KeyError
|
|
565
|
+
If the dependency does not exist and `default` was not provided.
|
|
566
|
+
"""
|
|
567
|
+
if default is hikari.UNDEFINED:
|
|
568
|
+
return self._injection_ctx.get_type_dependency(type_)
|
|
569
|
+
else:
|
|
570
|
+
return self._injection_ctx.get_type_dependency(type_, default=default)
|
|
571
|
+
|
|
533
572
|
async def get_last_response(self) -> InteractionResponse:
|
|
534
573
|
"""Get the last response issued to the interaction this context is proxying.
|
|
535
574
|
|
|
@@ -652,14 +691,12 @@ class Context(t.Generic[ClientT]):
|
|
|
652
691
|
return response
|
|
653
692
|
|
|
654
693
|
@t.overload
|
|
655
|
-
async def respond_with_builder(self, builder: hikari.api.InteractionModalBuilder) -> None:
|
|
656
|
-
...
|
|
694
|
+
async def respond_with_builder(self, builder: hikari.api.InteractionModalBuilder) -> None: ...
|
|
657
695
|
|
|
658
696
|
@t.overload
|
|
659
697
|
async def respond_with_builder(
|
|
660
698
|
self, builder: hikari.api.InteractionMessageBuilder | hikari.api.InteractionDeferredBuilder
|
|
661
|
-
) -> InteractionResponse:
|
|
662
|
-
...
|
|
699
|
+
) -> InteractionResponse: ...
|
|
663
700
|
|
|
664
701
|
async def respond_with_builder(self, builder: ResponseBuilderT) -> InteractionResponse | None:
|
|
665
702
|
"""Respond to the interaction with a builder. This method will try to turn the builder into a valid
|
arc/errors.py
CHANGED
|
@@ -7,6 +7,7 @@ if t.TYPE_CHECKING:
|
|
|
7
7
|
|
|
8
8
|
from arc.abc.concurrency_limiting import ConcurrencyLimiterProto
|
|
9
9
|
from arc.abc.limiter import LimiterProto
|
|
10
|
+
from arc.abc.option import ConverterOption
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class ArcError(Exception):
|
|
@@ -162,6 +163,23 @@ class GuildCommandPublishFailedError(CommandPublishFailedError):
|
|
|
162
163
|
super().__init__(*args)
|
|
163
164
|
|
|
164
165
|
|
|
166
|
+
class OptionConverterFailureError(ArcError):
|
|
167
|
+
"""Raised when an option converter fails to convert a value.
|
|
168
|
+
|
|
169
|
+
Attributes
|
|
170
|
+
----------
|
|
171
|
+
option : arc.abc.option.ConverterOption
|
|
172
|
+
The option that failed to convert the value.
|
|
173
|
+
value : t.Any
|
|
174
|
+
The value that failed to be converted.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
def __init__(self, option: ConverterOption[t.Any, t.Any, t.Any, t.Any], value: t.Any, *args: t.Any) -> None:
|
|
178
|
+
self.option = option
|
|
179
|
+
self.value = value
|
|
180
|
+
super().__init__(*args)
|
|
181
|
+
|
|
182
|
+
|
|
165
183
|
# MIT License
|
|
166
184
|
#
|
|
167
185
|
# Copyright (c) 2023-present hypergonial
|
arc/extension.py
CHANGED
|
@@ -8,13 +8,11 @@ if t.TYPE_CHECKING:
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@t.overload
|
|
11
|
-
def loader() -> t.Callable[[t.Callable[[ClientT], None]], t.Callable[[ClientT], None]]:
|
|
12
|
-
...
|
|
11
|
+
def loader() -> t.Callable[[t.Callable[[ClientT], None]], t.Callable[[ClientT], None]]: ...
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
@t.overload
|
|
16
|
-
def loader(callback: t.Callable[[ClientT], None]) -> t.Callable[[ClientT], None]:
|
|
17
|
-
...
|
|
15
|
+
def loader(callback: t.Callable[[ClientT], None]) -> t.Callable[[ClientT], None]: ...
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
def loader(
|
|
@@ -51,13 +49,11 @@ def loader(
|
|
|
51
49
|
|
|
52
50
|
|
|
53
51
|
@t.overload
|
|
54
|
-
def unloader() -> t.Callable[[t.Callable[[ClientT], None]], t.Callable[[ClientT], None]]:
|
|
55
|
-
...
|
|
52
|
+
def unloader() -> t.Callable[[t.Callable[[ClientT], None]], t.Callable[[ClientT], None]]: ...
|
|
56
53
|
|
|
57
54
|
|
|
58
55
|
@t.overload
|
|
59
|
-
def unloader(callback: t.Callable[[ClientT], None]) -> t.Callable[[ClientT], None]:
|
|
60
|
-
...
|
|
56
|
+
def unloader(callback: t.Callable[[ClientT], None]) -> t.Callable[[ClientT], None]: ...
|
|
61
57
|
|
|
62
58
|
|
|
63
59
|
def unloader(
|
arc/internal/about.py
CHANGED
|
@@ -5,7 +5,7 @@ __author_email__: t.Final[str] = "git@hypergonial.com"
|
|
|
5
5
|
__maintainer__: t.Final[str] = "hypergonial"
|
|
6
6
|
__license__: t.Final[str] = "MIT"
|
|
7
7
|
__url__: t.Final[str] = "https://github.com/hypergonial/hikari-arc"
|
|
8
|
-
__version__: t.Final[str] = "1.
|
|
8
|
+
__version__: t.Final[str] = "1.3.1"
|
|
9
9
|
|
|
10
10
|
# MIT License
|
|
11
11
|
#
|
arc/internal/options.py
CHANGED
|
@@ -4,7 +4,7 @@ import typing as t
|
|
|
4
4
|
|
|
5
5
|
import hikari
|
|
6
6
|
|
|
7
|
-
from arc.abc.option import OptionType
|
|
7
|
+
from arc.abc.option import ConverterOption, OptionType
|
|
8
8
|
|
|
9
9
|
if t.TYPE_CHECKING:
|
|
10
10
|
from arc.abc.option import CommandOptionBase
|
|
@@ -21,6 +21,8 @@ OPTIONTYPE_TO_TYPE: dict[OptionType, type[t.Any]] = {
|
|
|
21
21
|
OptionType.MENTIONABLE: hikari.Unique,
|
|
22
22
|
OptionType.FLOAT: float,
|
|
23
23
|
OptionType.ATTACHMENT: hikari.Attachment,
|
|
24
|
+
OptionType.COLOR: hikari.Color,
|
|
25
|
+
OptionType.MEMBER: hikari.Member,
|
|
24
26
|
}
|
|
25
27
|
"""Used for runtime type checking in Context.get_option, not much else at the moment."""
|
|
26
28
|
|
|
@@ -89,18 +91,21 @@ def resolve_options(
|
|
|
89
91
|
"""
|
|
90
92
|
option_kwargs: dict[str, t.Any] = {}
|
|
91
93
|
|
|
92
|
-
for
|
|
94
|
+
for opt in local_options.values():
|
|
93
95
|
inter_opt = next((o for o in incoming_options if o.name == opt.name), None)
|
|
94
96
|
|
|
95
97
|
if inter_opt is None:
|
|
96
98
|
continue
|
|
97
99
|
|
|
98
100
|
if isinstance(inter_opt.value, hikari.Snowflake) and resolved:
|
|
99
|
-
option_kwargs[arg_name] = resolve_snowflake_value(inter_opt.value, inter_opt.type, resolved)
|
|
101
|
+
option_kwargs[opt.arg_name] = resolve_snowflake_value(inter_opt.value, inter_opt.type, resolved)
|
|
100
102
|
|
|
101
103
|
elif isinstance(inter_opt.value, hikari.Snowflake):
|
|
102
104
|
raise ValueError(f"Missing resolved option data for '{inter_opt.name}'.")
|
|
103
105
|
else:
|
|
104
|
-
option_kwargs[arg_name] = inter_opt.value
|
|
106
|
+
option_kwargs[opt.arg_name] = inter_opt.value
|
|
107
|
+
|
|
108
|
+
if isinstance(opt, ConverterOption):
|
|
109
|
+
option_kwargs[opt.arg_name] = opt._convert_value(option_kwargs[opt.arg_name]) # pyright: ignore
|
|
105
110
|
|
|
106
111
|
return option_kwargs
|
arc/internal/sigparse.py
CHANGED
|
@@ -15,10 +15,14 @@ from arc.command.option import (
|
|
|
15
15
|
BoolParams,
|
|
16
16
|
ChannelOption,
|
|
17
17
|
ChannelParams,
|
|
18
|
+
ColorOption,
|
|
19
|
+
ColorParams,
|
|
18
20
|
FloatOption,
|
|
19
21
|
FloatParams,
|
|
20
22
|
IntOption,
|
|
21
23
|
IntParams,
|
|
24
|
+
MemberOption,
|
|
25
|
+
MemberParams,
|
|
22
26
|
MentionableOption,
|
|
23
27
|
MentionableParams,
|
|
24
28
|
RoleOption,
|
|
@@ -46,6 +50,9 @@ TYPE_TO_OPTION_MAPPING: dict[type[t.Any], type[CommandOptionBase[t.Any, t.Any, t
|
|
|
46
50
|
float: FloatOption,
|
|
47
51
|
hikari.Role: RoleOption,
|
|
48
52
|
hikari.Attachment: AttachmentOption,
|
|
53
|
+
hikari.Member: MemberOption,
|
|
54
|
+
hikari.InteractionMember: MemberOption,
|
|
55
|
+
hikari.Color: ColorOption,
|
|
49
56
|
hikari.User: UserOption,
|
|
50
57
|
}
|
|
51
58
|
|
|
@@ -59,6 +66,8 @@ OPT_TO_PARAMS_MAPPING: dict[type[CommandOptionBase[t.Any, t.Any, t.Any]], type[t
|
|
|
59
66
|
MentionableOption: MentionableParams,
|
|
60
67
|
RoleOption: RoleParams,
|
|
61
68
|
AttachmentOption: AttachmentParams,
|
|
69
|
+
MemberOption: MemberParams,
|
|
70
|
+
ColorOption: ColorParams,
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
BASE_CHANNEL_TYPE_MAP: dict[type[hikari.PartialChannel], hikari.ChannelType] = {
|
|
@@ -305,15 +314,20 @@ def parse_command_signature( # noqa: C901
|
|
|
305
314
|
# If it's a union of channel types, we need to parse all channel types
|
|
306
315
|
if union is not None and any(arg in CHANNEL_TYPES_MAPPING for arg in t.get_args(union)):
|
|
307
316
|
channel_types = _parse_channel_union_type_hint(union)
|
|
308
|
-
options[arg_name] = ChannelOption._from_params(
|
|
309
|
-
name=params.name or arg_name,
|
|
317
|
+
options[params.name or arg_name] = ChannelOption._from_params(
|
|
318
|
+
name=params.name or arg_name,
|
|
319
|
+
arg_name=arg_name,
|
|
320
|
+
is_required=not is_optional,
|
|
321
|
+
params=params,
|
|
322
|
+
channel_types=channel_types,
|
|
310
323
|
)
|
|
311
324
|
continue
|
|
312
325
|
|
|
313
326
|
# If it's a single channel type, just pass the channel type
|
|
314
327
|
elif type_ in CHANNEL_TYPES_MAPPING:
|
|
315
|
-
options[arg_name] = ChannelOption._from_params(
|
|
328
|
+
options[params.name or arg_name] = ChannelOption._from_params(
|
|
316
329
|
name=params.name or arg_name,
|
|
330
|
+
arg_name=arg_name,
|
|
317
331
|
is_required=not is_optional,
|
|
318
332
|
params=params,
|
|
319
333
|
channel_types=_channels_to_channel_types([type_]),
|
|
@@ -321,8 +335,8 @@ def parse_command_signature( # noqa: C901
|
|
|
321
335
|
continue
|
|
322
336
|
|
|
323
337
|
# Otherwise just build the option
|
|
324
|
-
options[arg_name] = opt_type._from_params(
|
|
325
|
-
name=params.name or arg_name, is_required=not is_optional, params=params
|
|
338
|
+
options[params.name or arg_name] = opt_type._from_params(
|
|
339
|
+
name=params.name or arg_name, arg_name=arg_name, is_required=not is_optional, params=params
|
|
326
340
|
)
|
|
327
341
|
|
|
328
342
|
return options
|
arc/internal/sync.py
CHANGED
|
@@ -127,9 +127,9 @@ def _get_all_commands(
|
|
|
127
127
|
A mapping of guilds to command types to command names to commands that should be registered.
|
|
128
128
|
"""
|
|
129
129
|
# The big daddy of all mappings
|
|
130
|
-
mapping: dict[
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
mapping: dict[hikari.Snowflake | None, dict[hikari.CommandType, dict[str, CommandBase[t.Any, t.Any]]]] = (
|
|
131
|
+
defaultdict(lambda: defaultdict(lambda: defaultdict(dict))) # type: ignore
|
|
132
|
+
)
|
|
133
133
|
|
|
134
134
|
for command in itertools.chain(
|
|
135
135
|
client._slash_commands.values(), client._message_commands.values(), client._user_commands.values()
|
arc/internal/types.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import typing as t
|
|
4
4
|
|
|
5
5
|
if t.TYPE_CHECKING:
|
|
6
|
+
import alluka
|
|
6
7
|
import hikari
|
|
7
8
|
|
|
8
9
|
from arc.abc import Client, Hookable, HookResult, OptionParams
|
|
@@ -34,12 +35,13 @@ SlashCommandLike: t.TypeAlias = "SlashCommand[ClientT] | SlashGroup[ClientT]"
|
|
|
34
35
|
CommandCallbackT: t.TypeAlias = "t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]"
|
|
35
36
|
MessageCommandCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], hikari.Message], t.Awaitable[None]]"
|
|
36
37
|
UserCommandCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], hikari.User], t.Awaitable[None]]"
|
|
37
|
-
AutocompleteCallbackT: t.TypeAlias = "t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[ChoiceT]]] | t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[hikari.api.AutocompleteChoiceBuilder]]]"
|
|
38
|
+
AutocompleteCallbackT: t.TypeAlias = "t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[ChoiceT]]] | t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Mapping[str, ChoiceT]]] | t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[hikari.api.AutocompleteChoiceBuilder]]]"
|
|
38
39
|
ResponseBuilderT: t.TypeAlias = (
|
|
39
40
|
"hikari.api.InteractionMessageBuilder | hikari.api.InteractionDeferredBuilder | hikari.api.InteractionModalBuilder"
|
|
40
41
|
)
|
|
41
42
|
HookT: t.TypeAlias = "t.Callable[[Context[ClientT]], t.Awaitable[HookResult]] | t.Callable[[Context[ClientT]], HookResult] | t.Callable[[Context[ClientT]], None] | t.Callable[[Context[ClientT]], t.Awaitable[None]]"
|
|
42
43
|
PostHookT: t.TypeAlias = "t.Callable[[Context[ClientT]], None] | t.Callable[[Context[ClientT]], t.Awaitable[None]]"
|
|
44
|
+
InjectionHookT: t.TypeAlias = "t.Callable[[Context[ClientT], alluka.OverridingContext], None] | t.Callable[[Context[ClientT], alluka.OverridingContext], t.Awaitable[None]]"
|
|
43
45
|
LifeCycleHookT: t.TypeAlias = "t.Callable[[ClientT], t.Awaitable[None]]"
|
|
44
46
|
CommandLocaleRequestT: t.TypeAlias = "t.Callable[[CommandLocaleRequest], LocaleResponse]"
|
|
45
47
|
OptionLocaleRequestT: t.TypeAlias = "t.Callable[[OptionLocaleRequest], LocaleResponse]"
|
arc/utils/concurrency_limiter.py
CHANGED
|
@@ -166,7 +166,7 @@ class ConcurrencyLimiter(t.Generic[KeyT]):
|
|
|
166
166
|
This will block until a slot is available.
|
|
167
167
|
"""
|
|
168
168
|
key = self._get_key(item)
|
|
169
|
-
bucket = self._buckets.setdefault(key, _Bucket.for_limiter(key, self))
|
|
169
|
+
bucket = self._buckets.setdefault(key, _Bucket.for_limiter(key, self)) # pyright: ignore reportUnknowMemberType
|
|
170
170
|
await bucket.semaphore.acquire()
|
|
171
171
|
|
|
172
172
|
def release(self, item: KeyT) -> None:
|
arc/utils/loops.py
CHANGED
|
@@ -25,12 +25,13 @@ class _LoopBase(abc.ABC, t.Generic[P]):
|
|
|
25
25
|
- [`CronLoop`][arc.utils.loops.CronLoop]
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
__slots__ = ("_coro", "_task", "_failed", "_stop_next")
|
|
28
|
+
__slots__ = ("_coro", "_task", "_failed", "_stop_next", "_run_on_start")
|
|
29
29
|
|
|
30
|
-
def __init__(self, callback: t.Callable[P, t.Awaitable[None]]) -> None:
|
|
30
|
+
def __init__(self, callback: t.Callable[P, t.Awaitable[None]], *, run_on_start: bool = True) -> None:
|
|
31
31
|
self._coro = callback
|
|
32
32
|
self._task: asyncio.Task[None] | None = None
|
|
33
33
|
self._failed: int = 0
|
|
34
|
+
self._run_on_start: bool = run_on_start
|
|
34
35
|
self._stop_next: bool = False
|
|
35
36
|
|
|
36
37
|
if not inspect.iscoroutinefunction(self._coro):
|
|
@@ -51,23 +52,28 @@ class _LoopBase(abc.ABC, t.Generic[P]):
|
|
|
51
52
|
The number of seconds to wait before running the coroutine again.
|
|
52
53
|
"""
|
|
53
54
|
|
|
55
|
+
async def _call_callback(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
|
56
|
+
"""Call the callback and handle exceptions."""
|
|
57
|
+
try:
|
|
58
|
+
await self._coro(*args, **kwargs)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
traceback_msg = "\n".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
61
|
+
print(f"Loop encountered exception: {e}", file=sys.stderr)
|
|
62
|
+
print(traceback_msg, file=sys.stderr)
|
|
63
|
+
|
|
64
|
+
if self._failed < 3:
|
|
65
|
+
self._failed += 1
|
|
66
|
+
else:
|
|
67
|
+
raise RuntimeError(f"Loop failed repeatedly, stopping it. Exception: {e}")
|
|
68
|
+
|
|
54
69
|
async def _loopy_loop(self, *args: P.args, **kwargs: P.kwargs) -> None:
|
|
55
70
|
"""Main loop logic."""
|
|
71
|
+
if self._run_on_start:
|
|
72
|
+
await self._call_callback(*args, **kwargs)
|
|
73
|
+
|
|
56
74
|
while not self._stop_next:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
except Exception as e:
|
|
60
|
-
traceback_msg = "\n".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
61
|
-
print(f"Loop encountered exception: {e}", file=sys.stderr)
|
|
62
|
-
print(traceback_msg, file=sys.stderr)
|
|
63
|
-
|
|
64
|
-
if self._failed < 3:
|
|
65
|
-
self._failed += 1
|
|
66
|
-
await asyncio.sleep(self._get_next_run())
|
|
67
|
-
else:
|
|
68
|
-
raise RuntimeError(f"Loop failed repeatedly, stopping it. Exception: {e}")
|
|
69
|
-
else:
|
|
70
|
-
await asyncio.sleep(self._get_next_run())
|
|
75
|
+
await asyncio.sleep(self._get_next_run())
|
|
76
|
+
await self._call_callback(*args, **kwargs)
|
|
71
77
|
self.cancel()
|
|
72
78
|
|
|
73
79
|
def _create_task(self, *args: P.args, **kwargs: P.kwargs) -> asyncio.Task[None]:
|
|
@@ -135,6 +141,9 @@ class IntervalLoop(_LoopBase[P]):
|
|
|
135
141
|
The number of hours to wait before running the coroutine again.
|
|
136
142
|
days : float, optional
|
|
137
143
|
The number of days to wait before running the coroutine again.
|
|
144
|
+
run_on_start : bool, optional
|
|
145
|
+
Whether to run the callback immediately after starting the loop.
|
|
146
|
+
If set to false, the loop will wait for the specified interval before first running the callback.
|
|
138
147
|
|
|
139
148
|
Raises
|
|
140
149
|
------
|
|
@@ -164,8 +173,9 @@ class IntervalLoop(_LoopBase[P]):
|
|
|
164
173
|
minutes: float | None = None,
|
|
165
174
|
hours: float | None = None,
|
|
166
175
|
days: float | None = None,
|
|
176
|
+
run_on_start: bool = True,
|
|
167
177
|
) -> None:
|
|
168
|
-
super().__init__(callback)
|
|
178
|
+
super().__init__(callback, run_on_start=run_on_start)
|
|
169
179
|
if not seconds and not minutes and not hours and not days:
|
|
170
180
|
raise ValueError("At least one of 'seconds', 'minutes', 'hours' or 'days' must be not None.")
|
|
171
181
|
else:
|
|
@@ -196,6 +206,8 @@ class CronLoop(_LoopBase[P]):
|
|
|
196
206
|
The coroutine to run at the specified interval.
|
|
197
207
|
cron_format : str
|
|
198
208
|
The cron format to use. See https://en.wikipedia.org/wiki/Cron for more information.
|
|
209
|
+
timezone : datetime.timezone
|
|
210
|
+
The timezone to use for the cron format. Defaults to UTC.
|
|
199
211
|
|
|
200
212
|
Raises
|
|
201
213
|
------
|
|
@@ -217,20 +229,30 @@ class CronLoop(_LoopBase[P]):
|
|
|
217
229
|
create a [`CronLoop`][arc.utils.loops.CronLoop] from a coroutine function.
|
|
218
230
|
"""
|
|
219
231
|
|
|
220
|
-
__slots__ = ("_iter",)
|
|
232
|
+
__slots__ = ("_iter", "_tz")
|
|
221
233
|
|
|
222
|
-
def __init__(
|
|
223
|
-
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
callback: t.Callable[P, t.Awaitable[None]],
|
|
237
|
+
cron_format: str,
|
|
238
|
+
*,
|
|
239
|
+
timezone: datetime.timezone = datetime.timezone.utc,
|
|
240
|
+
) -> None:
|
|
241
|
+
super().__init__(callback, run_on_start=False)
|
|
242
|
+
self._tz = timezone
|
|
224
243
|
|
|
225
244
|
try:
|
|
226
245
|
import croniter
|
|
227
246
|
|
|
228
|
-
self._iter = croniter.croniter(cron_format
|
|
247
|
+
self._iter = croniter.croniter(cron_format)
|
|
229
248
|
except ImportError:
|
|
230
249
|
raise ImportError("Missing dependency for CronLoop: 'croniter'")
|
|
231
250
|
|
|
232
251
|
def _get_next_run(self) -> float:
|
|
233
|
-
return
|
|
252
|
+
return (
|
|
253
|
+
self._iter.get_next(float, start_time=datetime.datetime.now(self._tz))
|
|
254
|
+
- datetime.datetime.now(self._tz).timestamp()
|
|
255
|
+
)
|
|
234
256
|
|
|
235
257
|
|
|
236
258
|
def interval_loop(
|
arc/utils/ratelimiter.py
CHANGED
|
@@ -184,7 +184,7 @@ class RateLimiter(t.Generic[KeyT]):
|
|
|
184
184
|
|
|
185
185
|
key = self.get_key(item)
|
|
186
186
|
# Get existing or insert new bucket
|
|
187
|
-
bucket = self._buckets.setdefault(key, _Bucket.for_limiter(key, self))
|
|
187
|
+
bucket = self._buckets.setdefault(key, _Bucket.for_limiter(key, self)) # pyright: ignore reportUnknowMemberType
|
|
188
188
|
|
|
189
189
|
if bucket.is_exhausted and not wait:
|
|
190
190
|
raise RateLimiterExhaustedError(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: hikari-arc
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: A command handler for hikari with a focus on type-safety and correctness.
|
|
5
5
|
Home-page: https://github.com/hypergonial/hikari-arc
|
|
6
6
|
Author: hypergonial
|
|
@@ -24,27 +24,27 @@ Requires-Python: >=3.10.0,<3.13
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: hikari >=2.0.0.dev122
|
|
27
|
-
Requires-Dist: alluka
|
|
27
|
+
Requires-Dist: alluka <0.4,>=0.3.0
|
|
28
28
|
Requires-Dist: attrs >=23.1
|
|
29
29
|
Requires-Dist: colorama ; sys_platform=="win32"
|
|
30
30
|
Provides-Extra: cron
|
|
31
|
-
Requires-Dist: croniter ==2.0.
|
|
32
|
-
Requires-Dist: types-croniter ==2.0.0.
|
|
31
|
+
Requires-Dist: croniter ==2.0.5 ; extra == 'cron'
|
|
32
|
+
Requires-Dist: types-croniter ==2.0.0.20240423 ; extra == 'cron'
|
|
33
33
|
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: ruff ==0.
|
|
35
|
-
Requires-Dist: pyright ==1.1.
|
|
36
|
-
Requires-Dist: nox ==
|
|
37
|
-
Requires-Dist: typing-extensions ==4.
|
|
38
|
-
Requires-Dist: pytest ==
|
|
39
|
-
Requires-Dist: pytest-asyncio ==0.23.
|
|
40
|
-
Requires-Dist: slotscheck ==0.
|
|
34
|
+
Requires-Dist: ruff ==0.4.4 ; extra == 'dev'
|
|
35
|
+
Requires-Dist: pyright ==1.1.364 ; extra == 'dev'
|
|
36
|
+
Requires-Dist: nox ==2024.4.15 ; extra == 'dev'
|
|
37
|
+
Requires-Dist: typing-extensions ==4.11.0 ; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest ==8.2.1 ; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-asyncio ==0.23.7 ; extra == 'dev'
|
|
40
|
+
Requires-Dist: slotscheck ==0.19.0 ; extra == 'dev'
|
|
41
41
|
Provides-Extra: docs
|
|
42
|
-
Requires-Dist: mkdocs-material[imaging] ~=9.5.
|
|
43
|
-
Requires-Dist: mkdocs ~=1.
|
|
44
|
-
Requires-Dist: mkdocstrings-python ~=1.
|
|
45
|
-
Requires-Dist: black ~=24.
|
|
42
|
+
Requires-Dist: mkdocs-material[imaging] ~=9.5.24 ; extra == 'docs'
|
|
43
|
+
Requires-Dist: mkdocs ~=1.6.0 ; extra == 'docs'
|
|
44
|
+
Requires-Dist: mkdocstrings-python ~=1.10.2 ; extra == 'docs'
|
|
45
|
+
Requires-Dist: black ~=24.4.2 ; extra == 'docs'
|
|
46
46
|
Requires-Dist: griffe-inherited-docstrings ~=1.0.0 ; extra == 'docs'
|
|
47
|
-
Requires-Dist: mkdocs-glightbox ~=0.
|
|
47
|
+
Requires-Dist: mkdocs-glightbox ~=0.4.0 ; extra == 'docs'
|
|
48
48
|
Provides-Extra: rest
|
|
49
49
|
Requires-Dist: hikari[server] >=2.0.0.dev122 ; extra == 'rest'
|
|
50
50
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
arc/__init__.py,sha256=jNbK-SPm7T-O5Ej555Obxni9SyfQ05Z2i_G_DwGVggw,6029
|
|
2
|
+
arc/__main__.py,sha256=ClAG2bqkzmJfKrEMYTVzi0O5--8eY_QFuNAqsMmwVQY,2012
|
|
3
|
+
arc/client.py,sha256=bvyxtIVomFGRNUgEP3-D_SPM2FoYx4lAYuwB4ThwW58,17852
|
|
4
|
+
arc/errors.py,sha256=_RLNY-iivsbogHlv_ofSU8TwoIewOGZ_ruf6EKtPvbY,6802
|
|
5
|
+
arc/events.py,sha256=WRXTLH9PBwU4GTu5rZS5AJ4fNxhDgSD9Rx3M-02FcDQ,2782
|
|
6
|
+
arc/extension.py,sha256=PSK7XcwPCDoUAyGWbNQlr3qDNGpijsZl97RWxteMp9E,3373
|
|
7
|
+
arc/locale.py,sha256=nEKKQi-oKdU8VZQdWdFTL-tNECwhtTVv3I3vTsU_1f8,4921
|
|
8
|
+
arc/plugin.py,sha256=AKc5lLU8eXjnKyiQxxs7wZm27qLI8kLCKQcX5CP8Lcs,8624
|
|
9
|
+
arc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
arc/abc/__init__.py,sha256=J9KxcN9gHHKW_GDrJD_5fu76Ya6l4XvPHe0Vdqza0Pk,2277
|
|
11
|
+
arc/abc/client.py,sha256=MPQe4ZHeZgxsR0en6GDh8u9CDtivoJgGh_RYAlI_Yu0,54162
|
|
12
|
+
arc/abc/command.py,sha256=rWaBt4pD2ivZdLRtqAcLrURrFSOBW3Oqdj1s_s9ALGw,28769
|
|
13
|
+
arc/abc/concurrency_limiting.py,sha256=NefJQgftC-WzQNYehzzqmVuFY4MNHwPfQ8B4wqvjvUs,4666
|
|
14
|
+
arc/abc/error_handler.py,sha256=RCuO35mibkncSPxUbaDcUPRzOXhS-et0Sa_7VKMjpuk,3702
|
|
15
|
+
arc/abc/hookable.py,sha256=x5SjMQ314nPdnWsTV6ZELeDCKQMck2e24Ybv7BripzI,5486
|
|
16
|
+
arc/abc/limiter.py,sha256=kAPzIcoXcanAFNWIRmdIxelJHT4lb6HVaIwiDcpOdWI,1766
|
|
17
|
+
arc/abc/option.py,sha256=NgIPl1mbSpxfoVt10V95aKDMAhdUE9fzTgxyQFZzx7s,12982
|
|
18
|
+
arc/abc/plugin.py,sha256=Kl3n43XB2HE6DcCsU4eZ5O37WqTCsTemwAfPioraEJ8,19703
|
|
19
|
+
arc/command/__init__.py,sha256=TrVhqqfVR9K_RyMbng9TKZuix4nVLbTYOb78GZNOd8U,2542
|
|
20
|
+
arc/command/message.py,sha256=b0DjJoIQV9FlfGx3uZQPAmHfv0cBHJoIhXfVhusT9OA,5944
|
|
21
|
+
arc/command/slash.py,sha256=kfOw26t7QdkfxGovf8pk4RaMmrTgvFekM87B33X31hc,35485
|
|
22
|
+
arc/command/user.py,sha256=XJV_6gYwE4vpwBFw2shlQUwUIJL972N0qZpNVoxXa2c,5996
|
|
23
|
+
arc/command/option/__init__.py,sha256=hEldXTprteztvDjUIq6oTEhD8ayN5Dwfg56E_18abhY,2117
|
|
24
|
+
arc/command/option/attachment.py,sha256=wXv6AJT5F0500PKkkh3inR3QQafrPuX5a4gKk72o2H8,3047
|
|
25
|
+
arc/command/option/bool.py,sha256=4dNCAZooDtzMXDdKPPQ3DFtY-EIXkmYMHRDZvATCxWs,2945
|
|
26
|
+
arc/command/option/channel.py,sha256=dAWm0JNiq6z4JKloZZy00Sh3jrcvxFn5NpJWt7iugWc,3761
|
|
27
|
+
arc/command/option/float.py,sha256=vonlUXPI8wID7Yh60WvmgW5mVYGlNtPMMMv5c4HAZUo,5185
|
|
28
|
+
arc/command/option/int.py,sha256=SJuJWrVzrrot-dkDZe5B12crV2pCMVD73jxJwhLm3HY,5135
|
|
29
|
+
arc/command/option/mentionable.py,sha256=QRzCFknERoNDHHqlzTRIsp-d307F2BhBIqHmlkRow9s,3153
|
|
30
|
+
arc/command/option/role.py,sha256=Tdb1ep7otySvPPZuxutTPyUimBd_ZirHcCWIS8TMXA8,2977
|
|
31
|
+
arc/command/option/str.py,sha256=DcovOnAVqQ3wnIdzXbxm8s_da6qcgtqWbbhwhNheAMo,5310
|
|
32
|
+
arc/command/option/user.py,sha256=4R0GtzX5dNJYL27OZQNT7_rcD-jrTSxRnuKgpNyQk5c,2965
|
|
33
|
+
arc/command/option/custom/__init__.py,sha256=rAAtOTZNLN0jKLncUH7kP35zpVyIMTuYvRazqG31axQ,225
|
|
34
|
+
arc/command/option/custom/color.py,sha256=_vLHbaEo78bUrQXqPi0qmbDtBMtFsbX2icTDp74I8EE,3517
|
|
35
|
+
arc/command/option/custom/member.py,sha256=5tK1weEryp9LH0RdE9XBzXKZt7mUdYCJwwTaXSGYfGk,3381
|
|
36
|
+
arc/context/__init__.py,sha256=MOc71Up8gUAN8WfZkIzu3lDQhwZlwZbWFYWy3V7yCVQ,1303
|
|
37
|
+
arc/context/autocomplete.py,sha256=YOu6leCKH0mfGVj0vmo4kj1_cWUM6c_vjgooymQ6sYY,3797
|
|
38
|
+
arc/context/base.py,sha256=MUZe8abJUwzFUsnv_WsxHPKDQswI4IUImyozjgnGDs8,39977
|
|
39
|
+
arc/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
arc/internal/__init__.py,sha256=kZKBSFOkaDLe5kBvxiCQ-MWRPUZ_GBZdWwINPMkSbvo,1436
|
|
41
|
+
arc/internal/about.py,sha256=AyaKo5EADmNJPkrNqjvdEswZXMy1LZyf67HwBLzL8Bw,1414
|
|
42
|
+
arc/internal/deprecation.py,sha256=Lnirv1z_oj6QJvbnd38TwHQnhHhFD2rTqqvH96pxiWE,2204
|
|
43
|
+
arc/internal/options.py,sha256=EODBho9BdHOgjqqOVAEEbwOkZJiVfjFDDpUztGKUdK4,3622
|
|
44
|
+
arc/internal/sigparse.py,sha256=EsewKxcidtuoY0clEAVh8nVGmTq5hvAHxqokGOAcZPk,13518
|
|
45
|
+
arc/internal/sync.py,sha256=ApiHD66Gi8BOSUcEKRiZ_n03u9MNkftNjSDZz5Wk1_M,12589
|
|
46
|
+
arc/internal/types.py,sha256=NXemzM6cR2pH2vV9CCr6CSZFJZNY_yaovzNifppUkUA,4365
|
|
47
|
+
arc/internal/version.py,sha256=bZFtIbhehFhsGU2yyTVHb8YIvCYhp9iyueTalCKFtsg,2201
|
|
48
|
+
arc/utils/__init__.py,sha256=vc8QYVVVOe95_kfWWb5lc8dFkJrs5SnpIJta_t0l3UI,2334
|
|
49
|
+
arc/utils/concurrency_limiter.py,sha256=7wz7bfzvCna5Ai50EAw1SaY8ZmZy-Bc68NwubhQExxc,12965
|
|
50
|
+
arc/utils/loops.py,sha256=nCKGy0tTwpR49gDAZJaVwetMgIBM_WbqIpEyDIqBcjE,12064
|
|
51
|
+
arc/utils/ratelimiter.py,sha256=YPETOjQOga8RazYoK3Ghueh2TsOdfkH7WM58dr3ybcU,9477
|
|
52
|
+
arc/utils/hooks/__init__.py,sha256=pXlAQ1zGxQV-bBeeL8sKRkUyO1PmEazT_a_XKtf7GFA,515
|
|
53
|
+
arc/utils/hooks/basic.py,sha256=e09raCnIcGMpBMd4uvzBCofNij2aGAmXPO2AuC8cXZY,5894
|
|
54
|
+
arc/utils/hooks/limiters.py,sha256=So6BZxSy3AdexM1UHWt8cILGQ8b-Fp78DYfqqw4yvdM,7496
|
|
55
|
+
hikari_arc-1.3.1.dist-info/LICENSE,sha256=q_osUjCCfQVI7zzgteLMZ-RlhXlB4rqQE8I0DGh7ur4,1076
|
|
56
|
+
hikari_arc-1.3.1.dist-info/METADATA,sha256=juQrw3lZ5xNQINwBHMfq0o0z7yFIWhJS_c6nn-M6ymQ,5563
|
|
57
|
+
hikari_arc-1.3.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
58
|
+
hikari_arc-1.3.1.dist-info/top_level.txt,sha256=kTs_REfGfSlIT6Hq_kxH-MtDlOO6LPwFwkOoNdDCnJ4,4
|
|
59
|
+
hikari_arc-1.3.1.dist-info/RECORD,,
|