hikari-arc 1.2.0__py3-none-any.whl → 1.3.0__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/command/option/str.py CHANGED
@@ -104,9 +104,12 @@ class StrOption(OptionWithChoices[str, ClientT, StrParams[ClientT]]):
104
104
  return OptionType.STRING
105
105
 
106
106
  @classmethod
107
- def _from_params(cls, *, name: str, is_required: bool, params: StrParams[ClientT], **kwargs: t.Any) -> te.Self:
107
+ def _from_params(
108
+ cls, *, name: str, arg_name: str, is_required: bool, params: StrParams[ClientT], **kwargs: t.Any
109
+ ) -> te.Self:
108
110
  return cls(
109
111
  name=name,
112
+ arg_name=arg_name,
110
113
  description=params.description,
111
114
  is_required=is_required,
112
115
  min_length=params.min_length,
@@ -53,9 +53,12 @@ class UserOption(CommandOptionBase[hikari.User, ClientT, UserParams]):
53
53
  return OptionType.USER
54
54
 
55
55
  @classmethod
56
- def _from_params(cls, *, name: str, is_required: bool, params: UserParams, **kwargs: t.Any) -> te.Self:
56
+ def _from_params(
57
+ cls, *, name: str, arg_name: str, is_required: bool, params: UserParams, **kwargs: t.Any
58
+ ) -> te.Self:
57
59
  return cls(
58
60
  name=name,
61
+ arg_name=arg_name,
59
62
  description=params.description,
60
63
  is_required=is_required,
61
64
  name_localizations=params.name_localizations,
arc/command/slash.py CHANGED
@@ -36,9 +36,12 @@ __all__ = (
36
36
 
37
37
 
38
38
  def _choices_to_builders(
39
- choices: t.Sequence[hikari.api.AutocompleteChoiceBuilder] | t.Sequence[t.Any],
39
+ choices: t.Sequence[hikari.api.AutocompleteChoiceBuilder] | t.Sequence[t.Any] | t.Mapping[str, t.Any],
40
40
  ) -> t.Sequence[hikari.api.AutocompleteChoiceBuilder]:
41
41
  """Convert a sequence of choices to a sequence of choice builders."""
42
+ if isinstance(choices, t.Mapping):
43
+ return [hikari.impl.AutocompleteChoiceBuilder(str(k), v) for k, v in choices.items()]
44
+
42
45
  return [
43
46
  (
44
47
  hikari.impl.AutocompleteChoiceBuilder(str(e), e)
@@ -403,12 +406,10 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
403
406
  sub._request_option_locale(self._client, self)
404
407
 
405
408
  @t.overload
406
- def include(self) -> t.Callable[[SlashSubCommand[ClientT]], SlashSubCommand[ClientT]]:
407
- ...
409
+ def include(self) -> t.Callable[[SlashSubCommand[ClientT]], SlashSubCommand[ClientT]]: ...
408
410
 
409
411
  @t.overload
410
- def include(self, command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]:
411
- ...
412
+ def include(self, command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]: ...
412
413
 
413
414
  def include(
414
415
  self, command: SlashSubCommand[ClientT] | None = None
@@ -518,7 +519,7 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
518
519
 
519
520
  return settings.apply(
520
521
  _CommandSettings(
521
- autodefer=self.autodefer,
522
+ autodefer=self._autodefer,
522
523
  default_permissions=hikari.UNDEFINED,
523
524
  is_nsfw=hikari.UNDEFINED,
524
525
  is_dm_enabled=hikari.UNDEFINED,
@@ -540,12 +541,12 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
540
541
  async def _handle_exception(self, ctx: Context[ClientT], exc: Exception) -> None:
541
542
  try:
542
543
  if self.error_handler:
543
- await self.error_handler(ctx, exc)
544
+ await ctx._injection_ctx.call_with_async_di(self.error_handler, ctx, exc)
544
545
  else:
545
546
  raise exc
546
- except Exception as e:
547
+ except Exception as exc:
547
548
  assert self._parent is not None
548
- await self._parent._handle_exception(ctx, e)
549
+ await ctx._injection_ctx.call_with_async_di(self._parent._handle_exception, ctx, exc)
549
550
 
550
551
  def _request_option_locale(self, client: Client[t.Any], command: CommandProto) -> None:
551
552
  super()._request_option_locale(client, command)
@@ -554,12 +555,10 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
554
555
  subcommand._request_option_locale(client, command)
555
556
 
556
557
  @t.overload
557
- def include(self) -> t.Callable[[SlashSubCommand[ClientT]], SlashSubCommand[ClientT]]:
558
- ...
558
+ def include(self) -> t.Callable[[SlashSubCommand[ClientT]], SlashSubCommand[ClientT]]: ...
559
559
 
560
560
  @t.overload
561
- def include(self, command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]:
562
- ...
561
+ def include(self, command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]: ...
563
562
 
564
563
  def include(
565
564
  self, command: SlashSubCommand[ClientT] | None = None
@@ -700,7 +699,7 @@ class SlashSubCommand(
700
699
  raise exc
701
700
  except Exception as e:
702
701
  assert self._parent is not None
703
- await self._parent._handle_exception(ctx, e)
702
+ await self._handle_exception(ctx, e)
704
703
 
705
704
  def _request_option_locale(self, client: Client[t.Any], command: CommandProto) -> None:
706
705
  super()._request_option_locale(client, command)
@@ -746,7 +745,7 @@ def slash_command(
746
745
  default_permissions: hikari.Permissions | hikari.UndefinedType = hikari.UNDEFINED,
747
746
  name_localizations: t.Mapping[hikari.Locale, str] | None = None,
748
747
  description_localizations: t.Mapping[hikari.Locale, str] | None = None,
749
- ) -> t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashCommand[ClientT]]:
748
+ ) -> t.Callable[[CommandCallbackT[ClientT]], SlashCommand[ClientT]]:
750
749
  """A decorator that creates a slash command.
751
750
 
752
751
  Parameters
@@ -791,7 +790,7 @@ def slash_command(
791
790
  ```
792
791
  """
793
792
 
794
- def decorator(func: t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]) -> SlashCommand[ClientT]:
793
+ def decorator(func: CommandCallbackT[ClientT]) -> SlashCommand[ClientT]:
795
794
  guild_ids = tuple(hikari.Snowflake(i) for i in guilds) if guilds is not hikari.UNDEFINED else hikari.UNDEFINED
796
795
  options = parse_command_signature(func)
797
796
 
@@ -819,7 +818,7 @@ def slash_subcommand(
819
818
  autodefer: bool | AutodeferMode | hikari.UndefinedType = hikari.UNDEFINED,
820
819
  name_localizations: t.Mapping[hikari.Locale, str] | None = None,
821
820
  description_localizations: t.Mapping[hikari.Locale, str] | None = None,
822
- ) -> t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashSubCommand[ClientT]]:
821
+ ) -> t.Callable[[CommandCallbackT[ClientT]], SlashSubCommand[ClientT]]:
823
822
  """A decorator that creates a slash sub command. It should be included in a slash command group.
824
823
 
825
824
  Parameters
@@ -859,9 +858,7 @@ def slash_subcommand(
859
858
  ```
860
859
  """
861
860
 
862
- def decorator(
863
- func: t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]],
864
- ) -> SlashSubCommand[ClientT]:
861
+ def decorator(func: CommandCallbackT[ClientT]) -> SlashSubCommand[ClientT]:
865
862
  options = parse_command_signature(func)
866
863
 
867
864
  return SlashSubCommand(
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):
@@ -162,14 +166,14 @@ class InteractionResponse:
162
166
 
163
167
  async def edit(
164
168
  self,
165
- content: t.Any | hikari.UndefinedType = hikari.UNDEFINED,
169
+ content: t.Any | hikari.UndefinedType | None = hikari.UNDEFINED,
166
170
  *,
167
- component: hikari.api.ComponentBuilder | hikari.UndefinedType = hikari.UNDEFINED,
168
- components: t.Sequence[hikari.api.ComponentBuilder] | hikari.UndefinedType = hikari.UNDEFINED,
169
- attachment: hikari.Resourceish | hikari.UndefinedType = hikari.UNDEFINED,
170
- attachments: t.Sequence[hikari.Resourceish] | hikari.UndefinedType = hikari.UNDEFINED,
171
- embed: hikari.Embed | hikari.UndefinedType = hikari.UNDEFINED,
172
- embeds: t.Sequence[hikari.Embed] | hikari.UndefinedType = hikari.UNDEFINED,
171
+ component: hikari.api.ComponentBuilder | hikari.UndefinedType | None = hikari.UNDEFINED,
172
+ components: t.Sequence[hikari.api.ComponentBuilder] | hikari.UndefinedType | None = hikari.UNDEFINED,
173
+ attachment: hikari.Resourceish | hikari.UndefinedType | None = hikari.UNDEFINED,
174
+ attachments: t.Sequence[hikari.Resourceish] | hikari.UndefinedType | None = hikari.UNDEFINED,
175
+ embed: hikari.Embed | hikari.UndefinedType | None = hikari.UNDEFINED,
176
+ embeds: t.Sequence[hikari.Embed] | hikari.UndefinedType | None = hikari.UNDEFINED,
173
177
  mentions_everyone: bool | hikari.UndefinedType = hikari.UNDEFINED,
174
178
  user_mentions: t.Sequence[hikari.Snowflakeish | hikari.PartialUser]
175
179
  | bool
@@ -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.ATTACHMENT]) -> hikari.Attachment | None:
417
- ...
422
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.COLOR]) -> hikari.Color | None: ...
418
423
 
419
424
  @t.overload
420
- def get_option(self, name: str, opt_type: t.Literal[OptionType.MENTIONABLE]) -> hikari.User | hikari.Role | None:
421
- ...
425
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.MEMBER]) -> hikari.Member | None: ...
422
426
 
423
427
  @t.overload
424
- def get_option(self, name: str, opt_type: t.Literal[OptionType.USER]) -> hikari.User | None:
425
- ...
428
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.ATTACHMENT]) -> hikari.Attachment | None: ...
426
429
 
427
430
  @t.overload
428
- def get_option(self, name: str, opt_type: t.Literal[OptionType.ROLE]) -> hikari.Role | None:
429
- ...
431
+ def get_option(
432
+ self, name: str, opt_type: t.Literal[OptionType.MENTIONABLE]
433
+ ) -> hikari.User | hikari.Role | None: ...
430
434
 
431
435
  @t.overload
432
- def get_option(self, name: str, opt_type: t.Literal[OptionType.CHANNEL]) -> hikari.PartialChannel | None:
433
- ...
436
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.USER]) -> hikari.User | None: ...
434
437
 
435
438
  @t.overload
436
- def get_option(self, name: str, opt_type: t.Literal[OptionType.STRING]) -> str | None:
437
- ...
439
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.ROLE]) -> hikari.Role | None: ...
438
440
 
439
441
  @t.overload
440
- def get_option(self, name: str, opt_type: t.Literal[OptionType.BOOLEAN]) -> bool | None:
441
- ...
442
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.CHANNEL]) -> hikari.PartialChannel | None: ...
442
443
 
443
444
  @t.overload
444
- def get_option(self, name: str, opt_type: t.Literal[OptionType.FLOAT]) -> float | None:
445
- ...
445
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.STRING]) -> str | None: ...
446
446
 
447
447
  @t.overload
448
- def get_option(self, name: str, opt_type: t.Literal[OptionType.INTEGER]) -> int | None:
449
- ...
448
+ def get_option(self, name: str, opt_type: t.Literal[OptionType.BOOLEAN]) -> bool | None: ...
449
+
450
+ @t.overload
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
@@ -1,11 +1,11 @@
1
1
  import typing as t
2
2
 
3
3
  __author__: t.Final[str] = "hypergonial"
4
- __author_email__: t.Final[str] = "46067571+hypergonial@users.noreply.github.com"
4
+ __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.2.0"
8
+ __version__: t.Final[str] = "1.3.0"
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 arg_name, opt in local_options.items():
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, is_required=not is_optional, params=params, channel_types=channel_types
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
- hikari.Snowflake | None, dict[hikari.CommandType, dict[str, CommandBase[t.Any, t.Any]]]
132
- ] = defaultdict(lambda: defaultdict(lambda: defaultdict(dict))) # type: ignore
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]"
@@ -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:
@@ -243,9 +243,14 @@ class CommandConcurrencyLimiter(ConcurrencyLimiter[Context[ClientT]], Concurrenc
243
243
  return super().__call__(item)
244
244
 
245
245
 
246
- def global_concurrency() -> CommandConcurrencyLimiter[t.Any]:
246
+ def global_concurrency(limit: int) -> CommandConcurrencyLimiter[t.Any]:
247
247
  """Limit a command to a certain amount of concurrent instances globally.
248
248
 
249
+ Parameters
250
+ ----------
251
+ limit : int
252
+ The maximum amount of concurrently running command instances.
253
+
249
254
  Returns
250
255
  -------
251
256
  CommandConcurrencyLimiter[t.Any]
@@ -254,10 +259,10 @@ def global_concurrency() -> CommandConcurrencyLimiter[t.Any]:
254
259
  Examples
255
260
  --------
256
261
  ```py
257
- @arc.with_concurrency_limit(arc.global_concurrency())
262
+ @arc.with_concurrency_limit(arc.global_concurrency(1))
258
263
  ```
259
264
  """
260
- return CommandConcurrencyLimiter(1, get_key_with=lambda _: "amongus")
265
+ return CommandConcurrencyLimiter(limit, get_key_with=lambda _: "amongus")
261
266
 
262
267
 
263
268
  def guild_concurrency(limit: int) -> CommandConcurrencyLimiter[t.Any]:
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,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hikari-arc
3
- Version: 1.2.0
3
+ Version: 1.3.0
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
7
- Author-email: 46067571+hypergonial@users.noreply.github.com
7
+ Author-email: git@hypergonial.com
8
8
  Maintainer: hypergonial
9
9
  License: MIT
10
10
  Classifier: Development Status :: 5 - Production/Stable
@@ -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 >=0.1.4
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.1 ; extra == 'cron'
32
- Requires-Dist: types-croniter ==2.0.0.20240106 ; extra == 'cron'
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.1.14 ; extra == 'dev'
35
- Requires-Dist: pyright ==1.1.348 ; extra == 'dev'
36
- Requires-Dist: nox ==2023.4.22 ; extra == 'dev'
37
- Requires-Dist: typing-extensions ==4.9.0 ; extra == 'dev'
38
- Requires-Dist: pytest ==7.4.4 ; extra == 'dev'
39
- Requires-Dist: pytest-asyncio ==0.23.3 ; extra == 'dev'
40
- Requires-Dist: slotscheck ==0.17.1 ; extra == 'dev'
34
+ Requires-Dist: ruff ==0.4.4 ; extra == 'dev'
35
+ Requires-Dist: pyright ==1.1.362 ; 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.0 ; extra == 'dev'
39
+ Requires-Dist: pytest-asyncio ==0.23.6 ; extra == 'dev'
40
+ Requires-Dist: slotscheck ==0.19.0 ; extra == 'dev'
41
41
  Provides-Extra: docs
42
- Requires-Dist: mkdocs-material[imaging] ~=9.5.5 ; extra == 'docs'
43
- Requires-Dist: mkdocs ~=1.5.3 ; extra == 'docs'
44
- Requires-Dist: mkdocstrings-python ~=1.8.0 ; extra == 'docs'
45
- Requires-Dist: black ~=23.12.1 ; extra == 'docs'
42
+ Requires-Dist: mkdocs-material[imaging] ~=9.5.21 ; extra == 'docs'
43
+ Requires-Dist: mkdocs ~=1.6.0 ; extra == 'docs'
44
+ Requires-Dist: mkdocstrings-python ~=1.10.0 ; 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.3.7 ; extra == 'docs'
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