Skip to content

Commit

Permalink
Improve custom client creation flow
Browse files Browse the repository at this point in the history
  • Loading branch information
hypergonial committed Jan 5, 2024
1 parent b2de0e2 commit 588aebf
Show file tree
Hide file tree
Showing 26 changed files with 147 additions and 44 deletions.
14 changes: 13 additions & 1 deletion arc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@
from arc import abc, command

from .abc import HookResult, Option, with_hook, with_post_hook
from .client import Client, GatewayClient, GatewayContext, GatewayPlugin, RESTClient, RESTContext, RESTPlugin
from .client import (
Client,
GatewayClient,
GatewayClientBase,
GatewayContext,
GatewayPlugin,
RESTClient,
RESTClientBase,
RESTContext,
RESTPlugin,
)
from .command import (
AttachmentParams,
BoolParams,
Expand Down Expand Up @@ -86,6 +96,8 @@
"slash_command",
"slash_subcommand",
"Client",
"GatewayClientBase",
"RESTClientBase",
"GatewayClient",
"RESTClient",
"ArcError",
Expand Down
44 changes: 31 additions & 13 deletions arc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@
from arc.errors import NoResponseIssuedError
from arc.events import CommandErrorEvent
from arc.internal.sigparse import parse_event_signature
from arc.internal.types import GatewayBotT, RESTBotT
from arc.plugin import GatewayPluginBase, RESTPluginBase

if t.TYPE_CHECKING:
import typing_extensions as te

from .internal.types import EventCallbackT, EventT, ResponseBuilderT

__all__ = ("GatewayClient", "RESTClient")
__all__ = (
"GatewayClientBase",
"RESTClientBase",
"GatewayClient",
"RESTClient",
"GatewayContext",
"RESTContext",
"RESTPlugin",
"GatewayPlugin",
)


T = t.TypeVar("T")
Expand All @@ -26,9 +36,9 @@
logger = logging.getLogger(__name__)


class GatewayClient(Client[hikari.GatewayBotAware]):
"""The default implementation for an arc client with `hikari.GatewayBotAware` support.
If you want to use a `hikari.RESTBotAware`, use [`RESTClient`][arc.client.RESTClient] instead.
class GatewayClientBase(Client[GatewayBotT]):
"""The base class for an arc client with Gateway support.
If you want to use a RESTBot, use [`RESTClientBase`][arc.client.RESTClientBase] instead.
Parameters
----------
Expand All @@ -48,6 +58,7 @@ class GatewayClient(Client[hikari.GatewayBotAware]):
import arc
bot = hikari.GatewayBot("TOKEN")
# Default client implementation
client = arc.GatewayClient(bot)
...
Expand All @@ -58,7 +69,7 @@ class GatewayClient(Client[hikari.GatewayBotAware]):

def __init__(
self,
app: hikari.GatewayBotAware,
app: GatewayBotT,
*,
default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None,
autosync: bool = True,
Expand Down Expand Up @@ -157,9 +168,9 @@ def decorator(func: EventCallbackT[EventT]) -> EventCallbackT[EventT]:
return decorator


class RESTClient(Client[hikari.RESTBotAware]):
"""The default implementation for an arc client with `hikari.RESTBotAware` support.
If you want to use `hikari.GatewayBotAware`, use [`GatewayClient`][arc.client.GatewayClient] instead.
class RESTClientBase(Client[RESTBotT]):
"""The base class for an arc client with REST support.
If you want to use GatewayBot, use [`GatewayClient`][arc.client.GatewayClientBase] instead.
Parameters
----------
Expand All @@ -180,6 +191,7 @@ class RESTClient(Client[hikari.RESTBotAware]):
import arc
bot = hikari.RESTBot("TOKEN")
# Default client implementation
client = arc.RESTClient(bot)
...
Expand All @@ -193,7 +205,7 @@ class RESTClient(Client[hikari.RESTBotAware]):

def __init__(
self,
app: hikari.RESTBotAware,
app: RESTBotT,
*,
default_enabled_guilds: t.Sequence[hikari.Snowflake] | None = None,
autosync: bool = True,
Expand Down Expand Up @@ -261,16 +273,22 @@ async def _on_restbot_autocomplete_interaction_create(
return builder


GatewayContext = Context[GatewayClient]
GatewayClient = GatewayClientBase[hikari.GatewayBotAware]
"""The default gateway client implementation. An alias for [`arc.GatewayClientBase[hikari.GatewayBotAware]`][arc.client.GatewayClientBase]."""

RESTClient = RESTClientBase[hikari.RESTBotAware]
"""The default REST client implementation. An alias for [`arc.RESTClientBase[hikari.RESTBotAware]`][arc.client.RESTClientBase]."""

GatewayContext = Context[GatewayClientBase[hikari.GatewayBotAware]]
"""A context using the default gateway client implementation. An alias for [`arc.Context[arc.GatewayClient]`][arc.context.base.Context]."""

RESTContext = Context[RESTClient]
RESTContext = Context[RESTClientBase[hikari.RESTBotAware]]
"""A context using the default REST client implementation. An alias for [`arc.Context[arc.RESTClient]`][arc.context.base.Context]."""

RESTPlugin = RESTPluginBase[RESTClient]
RESTPlugin = RESTPluginBase[RESTClientBase[hikari.RESTBotAware]]
"""A plugin using the default REST client implementation. An alias for [`arc.RESTPluginBase[arc.RESTClient]`][arc.plugin.RESTPluginBase]."""

GatewayPlugin = GatewayPluginBase[GatewayClient]
GatewayPlugin = GatewayPluginBase[GatewayClientBase[hikari.GatewayBotAware]]
"""An alias for [`arc.GatewayPluginBase[arc.GatewayClient]`][arc.plugin.GatewayPluginBase]."""

# MIT License
Expand Down
8 changes: 5 additions & 3 deletions arc/internal/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import hikari

from arc.abc import Client, Hookable, HookResult, OptionParams
from arc.client import GatewayClient, RESTClient
from arc.client import GatewayClientBase, RESTClientBase
from arc.command import SlashCommand, SlashGroup
from arc.context import AutocompleteData, Context
from arc.locale import CommandLocaleRequest, CustomLocaleRequest, LocaleResponse, OptionLocaleRequest
Expand All @@ -16,8 +16,10 @@
AppT = t.TypeVar("AppT", bound="hikari.RESTAware")
ChoiceT = t.TypeVar("ChoiceT", bound="int | float | str")
ClientT = t.TypeVar("ClientT", bound="Client[t.Any]")
GatewayClientT = t.TypeVar("GatewayClientT", bound="GatewayClient")
RESTClientT = t.TypeVar("RESTClientT", bound="RESTClient")
GatewayBotT = t.TypeVar("GatewayBotT", bound="hikari.GatewayBotAware")
RESTBotT = t.TypeVar("RESTBotT", bound="hikari.RESTBotAware")
GatewayClientT = t.TypeVar("GatewayClientT", bound="GatewayClientBase[t.Any]")
RESTClientT = t.TypeVar("RESTClientT", bound="RESTClientBase[t.Any]")
EventT = t.TypeVar("EventT", bound="hikari.Event")
BuilderT = t.TypeVar("BuilderT", bound="hikari.api.SlashCommandBuilder | hikari.api.ContextMenuCommandBuilder")
ParamsT = t.TypeVar("ParamsT", bound="OptionParams[t.Any]")
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ hide:

Here you can find all the changelogs for `hikari-arc`.

## v0.4.1
## Unreleased

- Add `GatewayClientBase` and `RESTClientBase` to aid in creating custom client types. Examples on how to do this have also been added to the repository.
- Fix `InteractionResponse.retrieve_message()` failing due to incorrect assertion.

## v0.4.0
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
bot = hikari.GatewayBot("...")

# Initialize arc with the bot:
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)


@client.include # Add command to client
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/context_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/context_menu

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)

# Context-menu commands cannot be put in groups, and do not support options.
# Note that you can only define a MAXIMUM of 5 user and 5 message commands per bot.
Expand Down
35 changes: 35 additions & 0 deletions examples/gateway/custom_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import hikari
import arc

# Define a custom bot class that extends hikari.GatewayBot
# Note that this is not strictly required, but is possible
class MyBot(hikari.GatewayBot):

def custom_method(self) -> str:
return "I'm doing stuff!"

# You should inherit from arc.GatewayClientBase
# and specify the bot class that your client will use
# If you do not have a custom bot class, you can use hikari.GatewayBot here
class MyClient(arc.GatewayClientBase[MyBot]):

def custom_client_method(self) -> None:
print(self.app.custom_method())

# Optional but recommended:
# Create type aliases for your context and plugin types
MyContext = arc.Context[MyClient]
MyPlugin = arc.GatewayPluginBase[MyClient]

# Create your bot instance and client instance
bot = MyBot("...", banner=None)
client = MyClient(bot)

# Use in commands like normal
@client.include()
@arc.slash_command("test", "My command description")
async def my_command(ctx: MyContext) -> None:
ctx.client.custom_client_method()
await ctx.respond(ctx.client.app.custom_method())

bot.run()
2 changes: 1 addition & 1 deletion examples/gateway/dependency_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, value: int) -> None:
self.value = value

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)

# Create a new instance of 'MyDatabase'
database = MyDatabase(value=0)
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/error_handling

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)

@client.include
@arc.slash_command("name", "description")
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/extension_example/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/plugin_extensions

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)

# Load all extensions located in the 'extensions' directory
client.load_extensions_from("extensions")
Expand Down
4 changes: 2 additions & 2 deletions examples/gateway/extension_example/extensions/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ async def bar_cmd(
# This will be called when the extension is loaded
# A loader must be present for the extension to be valid
@arc.loader
def loader(client: arc.GatewayClient) -> None:
def loader(client: arc.GatewayClientBase) -> None:
client.add_plugin(plugin)

# And this will be called when the extension is unloaded
# Including this is optional, but if not present, the extension cannot be unloaded
@arc.unloader
def unloader(client: arc.GatewayClient) -> None:
def unloader(client: arc.GatewayClientBase) -> None:
client.remove_plugin(plugin)
4 changes: 2 additions & 2 deletions examples/gateway/extension_example/extensions/foo.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ async def foo_cmd(
# This will be called when the extension is loaded
# A loader must be present for the extension to be valid
@arc.loader
def loader(client: arc.GatewayClient) -> None:
def loader(client: arc.GatewayClientBase) -> None:
client.add_plugin(plugin)

# And this will be called when the extension is unloaded
# Including this is optional, but if not present, the extension cannot be unloaded
@arc.unloader
def unloader(client: arc.GatewayClient) -> None:
def unloader(client: arc.GatewayClientBase) -> None:
client.remove_plugin(plugin)
2 changes: 1 addition & 1 deletion examples/gateway/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/hooks

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)

# Any function that takes a context as its first argument
# and returns None or HookResult is a valid hook
Expand Down
2 changes: 1 addition & 1 deletion examples/gateway/localization.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

bot = hikari.GatewayBot("...")
# Set the locales that the client will request in the provider callbacks.
client = arc.GatewayClient(bot, provided_locales=[hikari.Locale.EN_US, hikari.Locale.ES_ES])
client = arc.GatewayClientBase(bot, provided_locales=[hikari.Locale.EN_US, hikari.Locale.ES_ES])

# These are just examples, you can provide localizations from anywhere you want.
COMMAND_LOCALES = {
Expand Down
4 changes: 2 additions & 2 deletions examples/gateway/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/options

bot = hikari.GatewayBot("...")
client = arc.GatewayClient(bot)
client = arc.GatewayClientBase(bot)


@client.include
Expand Down Expand Up @@ -42,7 +42,7 @@ async def all_the_options(

# Define an autocompletion callback
# This can either return a list of the option type, or a list of hikari.CommandChoice
async def provide_opts(data: arc.AutocompleteData[arc.GatewayClient, str]) -> list[str]:
async def provide_opts(data: arc.AutocompleteData[arc.GatewayClientBase, str]) -> list[str]:
if data.focused_value and len(data.focused_value) > 20:
return ["That", "is", "so", "long!"]
return ["Short", "is", "better!"]
Expand Down
2 changes: 1 addition & 1 deletion examples/rest/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
bot = hikari.RESTBot("...")

# Initialize arc with the bot:
client = arc.RESTClient(bot)
client = arc.RESTClientBase(bot)


@client.include # Add command to client
Expand Down
2 changes: 1 addition & 1 deletion examples/rest/context_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/context_menu

bot = hikari.RESTBot("...")
client = arc.RESTClient(bot)
client = arc.RESTClientBase(bot)

# Context-menu commands cannot be put in groups, and do not support options.
# Note that you can only define a MAXIMUM of 5 user and 5 message commands per bot.
Expand Down
35 changes: 35 additions & 0 deletions examples/rest/custom_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import hikari
import arc

# Define a custom bot class that extends hikari.RESTBot
# Note that this is not strictly required, but is possible
class MyBot(hikari.RESTBot):

def custom_method(self) -> str:
return "I'm doing stuff!"

# You should inherit from arc.RESTClientBase
# and specify the bot class that your client will use
# If you do not have a custom bot class, you can use hikari.RESTBot here
class MyClient(arc.RESTClientBase[MyBot]):

def custom_client_method(self) -> None:
print(self.app.custom_method())

# Optional but recommended:
# Create type aliases for your context and plugin types
MyContext = arc.Context[MyClient]
MyPlugin = arc.RESTPluginBase[MyClient]

# Create your bot instance and client instance
bot = MyBot("...", banner=None)
client = MyClient(bot)

# Use in commands like normal
@client.include()
@arc.slash_command("test", "My command description")
async def my_command(ctx: MyContext) -> None:
ctx.client.custom_client_method()
await ctx.respond(ctx.client.app.custom_method())

bot.run()
2 changes: 1 addition & 1 deletion examples/rest/dependency_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, value: int) -> None:
self.value = value

bot = hikari.RESTBot("...")
client = arc.RESTClient(bot)
client = arc.RESTClientBase(bot)

# Create a new instance of 'MyDatabase'
database = MyDatabase(value=0)
Expand Down
2 changes: 1 addition & 1 deletion examples/rest/error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/error_handling

bot = hikari.RESTBot("...")
client = arc.RESTClient(bot)
client = arc.RESTClientBase(bot)

@client.include
@arc.slash_command("name", "description")
Expand Down
2 changes: 1 addition & 1 deletion examples/rest/extension_example/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Related documentation guide: https://arc.hypergonial.com/guides/plugin_extensions

bot = hikari.RESTBot("...")
client = arc.RESTClient(bot)
client = arc.RESTClientBase(bot)

# Load all extensions located in the 'extensions' directory
client.load_extensions_from("extensions")
Expand Down
Loading

0 comments on commit 588aebf

Please sign in to comment.