diff options
Diffstat (limited to 'bot/exts/backend')
| -rw-r--r-- | bot/exts/backend/branding/_cog.py | 10 | ||||
| -rw-r--r-- | bot/exts/backend/branding/_repository.py | 36 | ||||
| -rw-r--r-- | bot/exts/backend/error_handler.py | 6 | ||||
| -rw-r--r-- | bot/exts/backend/sync/_cog.py | 50 | ||||
| -rw-r--r-- | bot/exts/backend/sync/_syncers.py | 16 |
5 files changed, 59 insertions, 59 deletions
diff --git a/bot/exts/backend/branding/_cog.py b/bot/exts/backend/branding/_cog.py index 94429c172..5d194ec3e 100644 --- a/bot/exts/backend/branding/_cog.py +++ b/bot/exts/backend/branding/_cog.py @@ -269,7 +269,7 @@ class Branding(commands.Cog): log.debug(f"Sending event information event to channel: {channel_id} ({is_notification=}).") await self.bot.wait_until_guild_available() - channel: t.Optional[discord.TextChannel] = self.bot.get_channel(channel_id) + channel: discord.TextChannel | None = self.bot.get_channel(channel_id) if channel is None: log.warning(f"Cannot send event information: channel {channel_id} not found!") @@ -291,7 +291,7 @@ class Branding(commands.Cog): await channel.send(content=content, embed=embed) - async def enter_event(self, event: Event) -> t.Tuple[bool, bool]: + async def enter_event(self, event: Event) -> tuple[bool, bool]: """ Apply `event` assets and update information cache. @@ -331,7 +331,7 @@ class Branding(commands.Cog): return banner_success, icon_success - async def synchronise(self) -> t.Tuple[bool, bool]: + async def synchronise(self) -> tuple[bool, bool]: """ Fetch the current event and delegate to `enter_event`. @@ -353,7 +353,7 @@ class Branding(commands.Cog): return await self.enter_event(current_event) - async def populate_cache_events(self, events: t.List[Event]) -> None: + async def populate_cache_events(self, events: list[Event]) -> None: """ Clear `cache_events` and re-populate with names and durations of `events`. @@ -402,7 +402,7 @@ class Branding(commands.Cog): """ log.debug("Checking whether daemon should start.") - should_begin: t.Optional[bool] = await self.cache_information.get("daemon_active") # None if never set! + should_begin: bool | None = await self.cache_information.get("daemon_active") # None if never set! if should_begin: self.daemon_loop.start() diff --git a/bot/exts/backend/branding/_repository.py b/bot/exts/backend/branding/_repository.py index e14f0a1ef..db2061faa 100644 --- a/bot/exts/backend/branding/_repository.py +++ b/bot/exts/backend/branding/_repository.py @@ -1,11 +1,11 @@ import typing as t -from datetime import date, datetime +from datetime import UTC, date, datetime import frontmatter from bot.bot import Bot from bot.constants import Keys -from bot.errors import BrandingMisconfiguration +from bot.errors import BrandingMisconfigurationError from bot.log import get_logger # Base URL for requests into the branding repository. @@ -39,9 +39,9 @@ class RemoteObject: name: str # Filename. path: str # Path from repo root. type: str # Either 'file' or 'dir'. - download_url: t.Optional[str] # If type is 'dir', this is None! + download_url: str | None # If type is 'dir', this is None! - def __init__(self, dictionary: t.Dict[str, t.Any]) -> None: + def __init__(self, dictionary: dict[str, t.Any]) -> None: """Initialize by grabbing annotated attributes from `dictionary`.""" missing_keys = self.__annotations__.keys() - dictionary.keys() if missing_keys: @@ -54,8 +54,8 @@ class MetaFile(t.NamedTuple): """Attributes defined in a 'meta.md' file.""" is_fallback: bool - start_date: t.Optional[date] - end_date: t.Optional[date] + start_date: date | None + end_date: date | None description: str # Markdown event description. @@ -93,7 +93,7 @@ class BrandingRepository: def __init__(self, bot: Bot) -> None: self.bot = bot - async def fetch_directory(self, path: str, types: t.Container[str] = ("file", "dir")) -> t.Dict[str, RemoteObject]: + async def fetch_directory(self, path: str, types: t.Container[str] = ("file", "dir")) -> dict[str, RemoteObject]: """ Fetch directory found at `path` in the branding repository. @@ -137,7 +137,7 @@ class BrandingRepository: attrs, description = frontmatter.parse(raw_file, encoding="UTF-8") if not description: - raise BrandingMisconfiguration("No description found in 'meta.md'!") + raise BrandingMisconfigurationError("No description found in 'meta.md'!") if attrs.get("fallback", False): return MetaFile(is_fallback=True, start_date=None, end_date=None, description=description) @@ -146,12 +146,12 @@ class BrandingRepository: end_date_raw = attrs.get("end_date") if None in (start_date_raw, end_date_raw): - raise BrandingMisconfiguration("Non-fallback event doesn't have start and end dates defined!") + raise BrandingMisconfigurationError("Non-fallback event doesn't have start and end dates defined!") # We extend the configured month & day with an arbitrary leap year, allowing a datetime object to exist. # This may raise errors if misconfigured. We let the caller handle such cases. - start_date = datetime.strptime(f"{start_date_raw} {ARBITRARY_YEAR}", DATE_FMT).date() - end_date = datetime.strptime(f"{end_date_raw} {ARBITRARY_YEAR}", DATE_FMT).date() + start_date = datetime.strptime(f"{start_date_raw} {ARBITRARY_YEAR}", DATE_FMT).replace(tzinfo=UTC).date() + end_date = datetime.strptime(f"{end_date_raw} {ARBITRARY_YEAR}", DATE_FMT).replace(tzinfo=UTC).date() return MetaFile(is_fallback=False, start_date=start_date, end_date=end_date, description=description) @@ -166,15 +166,15 @@ class BrandingRepository: missing_assets = {"meta.md", "server_icons", "banners"} - contents.keys() if missing_assets: - raise BrandingMisconfiguration(f"Directory is missing following assets: {missing_assets}") + raise BrandingMisconfigurationError(f"Directory is missing following assets: {missing_assets}") server_icons = await self.fetch_directory(contents["server_icons"].path, types=("file",)) banners = await self.fetch_directory(contents["banners"].path, types=("file",)) if len(server_icons) == 0: - raise BrandingMisconfiguration("Found no server icons!") + raise BrandingMisconfigurationError("Found no server icons!") if len(banners) == 0: - raise BrandingMisconfiguration("Found no server banners!") + raise BrandingMisconfigurationError("Found no server banners!") meta_bytes = await self.fetch_file(contents["meta.md"].download_url) @@ -182,7 +182,7 @@ class BrandingRepository: return Event(directory.path, meta_file, list(banners.values()), list(server_icons.values())) - async def get_events(self) -> t.List[Event]: + async def get_events(self) -> list[Event]: """ Discover available events in the branding repository. @@ -196,7 +196,7 @@ class BrandingRepository: log.exception("Failed to fetch 'events' directory.") return [] - instances: t.List[Event] = [] + instances: list[Event] = [] for event_directory in event_directories.values(): log.trace(f"Attempting to construct event from directory: '{event_directory.path}'.") @@ -209,7 +209,7 @@ class BrandingRepository: return instances - async def get_current_event(self) -> t.Tuple[t.Optional[Event], t.List[Event]]: + async def get_current_event(self) -> tuple[Event | None, list[Event]]: """ Get the currently active event, or the fallback event. @@ -218,7 +218,7 @@ class BrandingRepository: The current event may be None in the case that no event is active, and no fallback event is found. """ - utc_now = datetime.utcnow() + utc_now = datetime.now(tz=UTC) log.debug(f"Finding active event for: {utc_now}.") # Construct an object in the arbitrary year for the purpose of comparison. diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 8883f7566..0c8938918 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -75,7 +75,7 @@ class ErrorHandler(Cog): elif isinstance(e, errors.CheckFailure): log.debug(debug_message) await self.handle_check_failure(ctx, e) - elif isinstance(e, (errors.CommandOnCooldown, errors.MaxConcurrencyReached)): + elif isinstance(e, errors.CommandOnCooldown | errors.MaxConcurrencyReached): log.debug(debug_message) await ctx.send(e) elif isinstance(e, errors.CommandInvokeError): @@ -154,7 +154,7 @@ class ErrorHandler(Cog): if command.startswith("shh"): await ctx.invoke(silence_command, duration_or_channel=channel, duration=duration, kick=kick) return True - elif command.startswith("unshh"): + if command.startswith("unshh"): await ctx.invoke(self.bot.get_command("unsilence"), channel=channel) return True return False @@ -319,7 +319,7 @@ class ErrorHandler(Cog): await ctx.send( "Sorry, it looks like I don't have the permissions or roles I need to do that." ) - elif isinstance(e, (ContextCheckFailure, errors.NoPrivateMessage)): + elif isinstance(e, ContextCheckFailure | errors.NoPrivateMessage): ctx.bot.stats.incr("errors.wrong_channel_or_dm_error") await ctx.send(e) diff --git a/bot/exts/backend/sync/_cog.py b/bot/exts/backend/sync/_cog.py index 8c7dbb54e..83ebb70d2 100644 --- a/bot/exts/backend/sync/_cog.py +++ b/bot/exts/backend/sync/_cog.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Dict +from typing import Any from discord import Member, Role, User from discord.ext import commands @@ -48,7 +48,7 @@ class Sync(Cog): for syncer in (_syncers.RoleSyncer, _syncers.UserSyncer): await syncer.sync(guild) - async def patch_user(self, user_id: int, json: Dict[str, Any], ignore_404: bool = False) -> None: + async def patch_user(self, user_id: int, json: dict[str, Any], ignore_404: bool = False) -> None: """Send a PATCH request to partially update a user in the database.""" try: await self.bot.api_client.patch(f"bot/users/{user_id}", json=json) @@ -65,13 +65,13 @@ class Sync(Cog): return await self.bot.api_client.post( - 'bot/roles', + "bot/roles", json={ - 'colour': role.colour.value, - 'id': role.id, - 'name': role.name, - 'permissions': role.permissions.value, - 'position': role.position, + "colour": role.colour.value, + "id": role.id, + "name": role.name, + "permissions": role.permissions.value, + "position": role.position, } ) @@ -81,7 +81,7 @@ class Sync(Cog): if role.guild.id != constants.Guild.id: return - await self.bot.api_client.delete(f'bot/roles/{role.id}') + await self.bot.api_client.delete(f"bot/roles/{role.id}") @Cog.listener() async def on_guild_role_update(self, before: Role, after: Role) -> None: @@ -98,13 +98,13 @@ class Sync(Cog): if was_updated: await self.bot.api_client.put( - f'bot/roles/{after.id}', + f"bot/roles/{after.id}", json={ - 'colour': after.colour.value, - 'id': after.id, - 'name': after.name, - 'permissions': after.permissions.value, - 'position': after.position, + "colour": after.colour.value, + "id": after.id, + "name": after.name, + "permissions": after.permissions.value, + "position": after.position, } ) @@ -121,11 +121,11 @@ class Sync(Cog): return packed = { - 'discriminator': int(member.discriminator), - 'id': member.id, - 'in_guild': True, - 'name': member.name, - 'roles': sorted(role.id for role in member.roles) + "discriminator": int(member.discriminator), + "id": member.id, + "in_guild": True, + "name": member.name, + "roles": sorted(role.id for role in member.roles) } got_error = False @@ -133,7 +133,7 @@ class Sync(Cog): try: # First try an update of the user to set the `in_guild` field and other # fields that may have changed since the last time we've seen them. - await self.bot.api_client.put(f'bot/users/{member.id}', json=packed) + await self.bot.api_client.put(f"bot/users/{member.id}", json=packed) except ResponseCodeError as e: # If we didn't get 404, something else broke - propagate it up. @@ -144,7 +144,7 @@ class Sync(Cog): if got_error: # If we got `404`, the user is new. Create them. - await self.bot.api_client.post('bot/users', json=packed) + await self.bot.api_client.post("bot/users", json=packed) @Cog.listener() async def on_member_remove(self, member: Member) -> None: @@ -176,18 +176,18 @@ class Sync(Cog): # A 404 likely means the user is in another guild. await self.patch_user(after.id, json=updated_information, ignore_404=True) - @commands.group(name='sync') + @commands.group(name="sync") @commands.has_permissions(administrator=True) async def sync_group(self, ctx: Context) -> None: """Run synchronizations between the bot and site manually.""" - @sync_group.command(name='roles') + @sync_group.command(name="roles") @commands.has_permissions(administrator=True) async def sync_roles_command(self, ctx: Context) -> None: """Manually synchronise the guild's roles with the roles on the site.""" await _syncers.RoleSyncer.sync(ctx.guild, ctx) - @sync_group.command(name='users') + @sync_group.command(name="users") @commands.has_permissions(administrator=True) async def sync_users_command(self, ctx: Context) -> None: """Manually synchronise the guild's users with the users on the site.""" diff --git a/bot/exts/backend/sync/_syncers.py b/bot/exts/backend/sync/_syncers.py index f68674f8d..cd7f5040d 100644 --- a/bot/exts/backend/sync/_syncers.py +++ b/bot/exts/backend/sync/_syncers.py @@ -17,8 +17,8 @@ CHUNK_SIZE = 1000 # These objects are declared as namedtuples because tuples are hashable, # something that we make use of when diffing site roles against guild roles. -_Role = namedtuple('Role', ('id', 'name', 'colour', 'permissions', 'position')) -_Diff = namedtuple('Diff', ('created', 'updated', 'deleted')) +_Role = namedtuple("Role", ("id", "name", "colour", "permissions", "position")) +_Diff = namedtuple("Diff", ("created", "updated", "deleted")) # Implementation of static abstract methods are not enforced if the subclass is never instantiated. @@ -46,7 +46,7 @@ class Syncer(abc.ABC): raise NotImplementedError # pragma: no cover @classmethod - async def sync(cls, guild: Guild, ctx: t.Optional[Context] = None) -> None: + async def sync(cls, guild: Guild, ctx: Context | None = None) -> None: """ Synchronise the database with the cache of `guild`. @@ -89,7 +89,7 @@ class RoleSyncer(Syncer): async def _get_diff(guild: Guild) -> _Diff: """Return the difference of roles between the cache of `guild` and the database.""" log.trace("Getting the diff for roles.") - roles = await bot.instance.api_client.get('bot/roles') + roles = await bot.instance.api_client.get("bot/roles") # Pack DB roles and guild roles into one common, hashable format. # They're hashable so that they're easily comparable with sets later. @@ -123,15 +123,15 @@ class RoleSyncer(Syncer): """Synchronise the database with the role cache of `guild`.""" log.trace("Syncing created roles...") for role in diff.created: - await bot.instance.api_client.post('bot/roles', json=role._asdict()) + await bot.instance.api_client.post("bot/roles", json=role._asdict()) log.trace("Syncing updated roles...") for role in diff.updated: - await bot.instance.api_client.put(f'bot/roles/{role.id}', json=role._asdict()) + await bot.instance.api_client.put(f"bot/roles/{role.id}", json=role._asdict()) log.trace("Syncing deleted roles...") for role in diff.deleted: - await bot.instance.api_client.delete(f'bot/roles/{role.id}') + await bot.instance.api_client.delete(f"bot/roles/{role.id}") class UserSyncer(Syncer): @@ -152,7 +152,7 @@ class UserSyncer(Syncer): # Store user fields which are to be updated. updated_fields = {} - def maybe_update(db_field: str, guild_value: t.Union[str, int]) -> None: + def maybe_update(db_field: str, guild_value: str | int) -> None: # Equalize DB user and guild user attributes. if db_user[db_field] != guild_value: # noqa: B023 updated_fields[db_field] = guild_value # noqa: B023 |