Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and improve rest lifetime #1230

Merged
merged 12 commits into from
Jan 1, 2023
1 change: 1 addition & 0 deletions changes/1230.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`RESTApp` and `RESTBucketManager` now need to be started and stopped by using `.start` and `.close`.
1 change: 1 addition & 0 deletions changes/1230.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Buckets across different authentications are not shared any more, which would lead to incorrect rate limiting.
5 changes: 5 additions & 0 deletions changes/1230.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
`RESTClientImpl` improvements:
- You can now share client sessions and bucket managers across these objects or have them created for you.
- Speedup of request lifetime
- No-ratelimit routes no longer attempt to acquire rate limits
- Just for safety, a check is in place to treat the route as a rate limited route if a bucket is ever received for it and a error log is emitted. If you spot it around, please inform us!
78 changes: 78 additions & 0 deletions examples/oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Hikari Examples - A collection of examples for Hikari.
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain worldwide.
# This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
"""An example OAuth server."""
import logging
import os

from aiohttp import web

import hikari

logging.basicConfig(level=logging.DEBUG)

host = "localhost"
port = 8080
CLIENT_ID = int(os.environ["CLIENT_ID"]) # ID as an int
CLIENT_SECRET = os.environ["CLIENT_SECRET"] # Secret as a str
BOT_TOKEN = os.environ["BOT_TOKEN"] # Token as a str
CHANNEL_ID = int(os.environ["CHANNEL_ID"]) # Channel to post in as an int
REDIRECT_URI = "http://localhost:8080"

route_table = web.RouteTableDef()


@route_table.get("/")
async def oauth(request: web.Request) -> web.Response:
"""Handle an OAuth request."""
code = request.query.get("code")
if not code:
return web.json_response({"error": "'code' is not provided"}, status=400)

discord_rest: hikari.RESTApp = request.app["discord_rest"]

# Exchange code to acquire a Bearer one for the user
async with discord_rest.acquire(None) as r:
auth = await r.authorize_access_token(CLIENT_ID, CLIENT_SECRET, code, REDIRECT_URI)

# Perform a request as the user to get their own user object
async with discord_rest.acquire(auth.access_token, hikari.TokenType.BEARER) as client:
user = await client.fetch_my_user()
# user is a hikari.OwnUser object where we can access attributes on it

# Notify the success
async with discord_rest.acquire(BOT_TOKEN, hikari.TokenType.BOT) as client:
await client.create_message(CHANNEL_ID, f"{user} ({user.id}) just authorized!")

return web.Response(text="Successfully authenticated!")


async def start_discord_rest(app: web.Application) -> None:
"""Start the RESTApp."""
discord_rest = hikari.RESTApp()
await discord_rest.start()

app["discord_rest"] = discord_rest


async def stop_discord_rest(app: web.Application) -> None:
"""Stop the RESTApp."""
discord_rest: hikari.RESTApp = app["discord_rest"]

await discord_rest.close()


if __name__ == "__main__":
server = web.Application()
server.add_routes(route_table)

server.on_startup.append(start_discord_rest)
server.on_cleanup.append(stop_discord_rest)

web.run_app(server, host=host, port=port)
39 changes: 19 additions & 20 deletions hikari/impl/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,21 @@ def _validate_activity(activity: undefined.UndefinedNoneOr[presences.Activity])
)


async def _close_resource(name: str, awaitable: typing.Awaitable[typing.Any]) -> None:
future = asyncio.ensure_future(awaitable)

try:
await future
except Exception as ex:
asyncio.get_running_loop().call_exception_handler(
{
"message": f"{name} raised an exception during shut down",
"future": future,
"exception": ex,
}
)


class GatewayBot(traits.GatewayBotAware):
"""Basic auto-sharding bot implementation.

Expand Down Expand Up @@ -288,7 +303,7 @@ def __init__(
intents: intents_.Intents = intents_.Intents.ALL_UNPRIVILEGED,
auto_chunk_members: bool = True,
logs: typing.Union[None, int, str, typing.Dict[str, typing.Any]] = "INFO",
max_rate_limit: float = 300,
max_rate_limit: float = 300.0,
max_retries: int = 3,
proxy_settings: typing.Optional[config_impl.ProxySettings] = None,
rest_url: typing.Optional[str] = None,
Expand Down Expand Up @@ -425,30 +440,14 @@ async def close(self) -> None:
await self._event_manager.dispatch(self._event_factory.deserialize_stopping_event())
_LOGGER.log(ux.TRACE, "StoppingEvent dispatch completed, now beginning termination")

loop = asyncio.get_running_loop()

async def handle(name: str, awaitable: typing.Awaitable[typing.Any]) -> None:
future = asyncio.ensure_future(awaitable)

try:
await future
except Exception as ex:
loop.call_exception_handler(
{
"message": f"{name} raised an exception during shut down",
"future": future,
"exception": ex,
}
)

await handle("voice handler", self._voice.close())
await _close_resource("voice handler", self._voice.close())

shards = tuple((handle(f"shard {s.id}", s.close()) for s in self._shards.values() if s.is_alive))
shards = tuple(_close_resource(f"shard {s.id}", s.close()) for s in self._shards.values() if s.is_alive)

for coro in asyncio.as_completed(shards):
await coro

await handle("rest", self._rest.close())
await _close_resource("rest", self._rest.close())

# Clear out cache and shard map
self._cache.clear()
Expand Down
Loading