From 0186a0793aea064ae72f46c153f0a265da609296 Mon Sep 17 00:00:00 2001 From: stijndcl Date: Sun, 28 Aug 2022 22:15:03 +0200 Subject: [PATCH] fix typing issues --- didier/cogs/currency.py | 13 ++++--- didier/cogs/school.py | 23 ++++++++++++ didier/data/apis/hydra.py | 2 +- didier/data/embeds/base.py | 2 +- didier/data/embeds/deadlines.py | 2 +- didier/data/embeds/google/google_search.py | 2 +- didier/data/embeds/hydra/__init__.py | 4 +- didier/data/embeds/hydra/menu.py | 24 +++++++++--- didier/data/embeds/ufora/announcements.py | 2 +- didier/data/embeds/urban_dictionary.py | 2 +- didier/data/embeds/wordle.py | 2 +- didier/utils/http/requests.py | 43 +++++++++++++++------- 12 files changed, 88 insertions(+), 33 deletions(-) diff --git a/didier/cogs/currency.py b/didier/cogs/currency.py index 032fb7f..f7204a8 100644 --- a/didier/cogs/currency.py +++ b/didier/cogs/currency.py @@ -27,10 +27,13 @@ class Currency(commands.Cog): @commands.command(name="Award") @commands.check(is_owner) - async def award(self, ctx: commands.Context, user: discord.User, amount: abbreviated_number): # type: ignore + async def award( + self, + ctx: commands.Context, + user: discord.User, + amount: typing.Annotated[int, abbreviated_number], + ): """Award a user a given amount of Didier Dinks""" - amount = typing.cast(int, amount) - async with self.client.postgres_session as session: await crud.add_dinks(session, user.id, amount) plural = pluralize("Didier Dink", amount) @@ -116,10 +119,8 @@ class Currency(commands.Cog): await ctx.reply(f"**{ctx.author.display_name}** has **{bank.dinks}** {plural}.", mention_author=False) @commands.command(name="Invest", aliases=["Deposit", "Dep"]) - async def invest(self, ctx: commands.Context, amount: abbreviated_number): # type: ignore + async def invest(self, ctx: commands.Context, amount: typing.Annotated[typing.Union[str, int], abbreviated_number]): """Invest a given amount of Didier Dinks""" - amount = typing.cast(typing.Union[str, int], amount) - async with self.client.postgres_session as session: invested = await crud.invest(session, ctx.author.id, amount) plural = pluralize("Didier Dink", invested) diff --git a/didier/cogs/school.py b/didier/cogs/school.py index 460f2b2..6fcee75 100644 --- a/didier/cogs/school.py +++ b/didier/cogs/school.py @@ -1,3 +1,6 @@ +from datetime import datetime +from typing import Optional + import discord from discord import app_commands from discord.ext import commands @@ -5,7 +8,10 @@ from discord.ext import commands from database.crud import ufora_courses from database.crud.deadlines import get_deadlines from didier import Didier +from didier.data.apis.hydra import fetch_menu from didier.data.embeds.deadlines import Deadlines +from didier.data.embeds.hydra import no_menu_found +from didier.exceptions import HTTPException from didier.utils.discord.flags.school import StudyGuideFlags @@ -26,6 +32,23 @@ class School(commands.Cog): embed = Deadlines(deadlines).to_embed() await ctx.reply(embed=embed, mention_author=False, ephemeral=False) + @commands.hybrid_command( + name="menu", description="Show the menu in the Ghent University restaurants", aliases=["Eten", "Food"] + ) + async def menu(self, ctx: commands.Context, day: Optional[str] = None): + """Get the menu for a given day in the restaurants""" + # TODO time converter (transformer) for [DAY] + # TODO autocompletion for [DAY] + async with ctx.typing(): + day_dt = datetime.now() + + try: + menu = await fetch_menu(self.client.http_session, day_dt) + embed = menu.to_embed(day_dt=day_dt) + except HTTPException: + embed = no_menu_found(day_dt) + await ctx.reply(embed=embed, mention_author=False) + @commands.hybrid_command( name="fiche", description="Sends the link to the study guide for [Course]", aliases=["guide", "studiefiche"] ) diff --git a/didier/data/apis/hydra.py b/didier/data/apis/hydra.py index 303801e..f570986 100644 --- a/didier/data/apis/hydra.py +++ b/didier/data/apis/hydra.py @@ -11,5 +11,5 @@ __all__ = ["fetch_menu"] async def fetch_menu(http_session: ClientSession, day_dt: datetime) -> Menu: """Fetch the menu for a given day""" endpoint = f"https://hydra.ugent.be/api/2.0/resto/menu/nl/{day_dt.year}/{day_dt.month}/{day_dt.day}.json" - async with ensure_get(http_session, endpoint) as response: + async with ensure_get(http_session, endpoint, log_exceptions=False) as response: return Menu.parse_obj(response) diff --git a/didier/data/embeds/base.py b/didier/data/embeds/base.py index 45be7a0..4b32224 100644 --- a/didier/data/embeds/base.py +++ b/didier/data/embeds/base.py @@ -13,7 +13,7 @@ class EmbedBaseModel(ABC): """Abstract base class for a model that can be turned into a Discord embed""" @abstractmethod - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: """Turn this model into a Discord embed""" raise NotImplementedError diff --git a/didier/data/embeds/deadlines.py b/didier/data/embeds/deadlines.py index ac6f85d..371eee9 100644 --- a/didier/data/embeds/deadlines.py +++ b/didier/data/embeds/deadlines.py @@ -22,7 +22,7 @@ class Deadlines(EmbedBaseModel): self.deadlines.sort(key=lambda deadline: deadline.deadline) @overrides - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: embed = discord.Embed(title="Upcoming Deadlines", colour=discord.Colour.dark_gold()) now = tz_aware_now() diff --git a/didier/data/embeds/google/google_search.py b/didier/data/embeds/google/google_search.py index 59f26dd..dd0383b 100644 --- a/didier/data/embeds/google/google_search.py +++ b/didier/data/embeds/google/google_search.py @@ -32,7 +32,7 @@ class GoogleSearch(EmbedBaseModel): return embed @overrides - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: if not self.data.results or self.data.status_code != HTTPStatus.OK: return self._error_embed() diff --git a/didier/data/embeds/hydra/__init__.py b/didier/data/embeds/hydra/__init__.py index 1e18cda..798cd46 100644 --- a/didier/data/embeds/hydra/__init__.py +++ b/didier/data/embeds/hydra/__init__.py @@ -1,3 +1,3 @@ -from .menu import Menu +from .menu import Menu, no_menu_found -__all__ = ["Menu"] +__all__ = ["Menu", "no_menu_found"] diff --git a/didier/data/embeds/hydra/menu.py b/didier/data/embeds/hydra/menu.py index d42f2db..d40298a 100644 --- a/didier/data/embeds/hydra/menu.py +++ b/didier/data/embeds/hydra/menu.py @@ -1,4 +1,5 @@ -from typing import Literal, Optional +from datetime import datetime +from typing import Literal, Optional, cast import discord from overrides import overrides @@ -6,8 +7,10 @@ from pydantic import BaseModel from didier.data.embeds.base import EmbedPydantic from didier.utils.discord.colours import ghent_university_blue +from didier.utils.types.datetime import int_to_weekday +from didier.utils.types.string import leading -__all__ = ["Menu"] +__all__ = ["Menu", "no_menu_found"] class _Meal(BaseModel): @@ -16,7 +19,7 @@ class _Meal(BaseModel): kind: Literal["meat", "fish", "soup", "vegetarian", "vegan"] name: str price: str - type: Literal["main", "side"] + type: Literal["cold", "main", "side"] class Menu(EmbedPydantic): @@ -28,7 +31,18 @@ class Menu(EmbedPydantic): message: Optional[str] = None @overrides - def to_embed(self, **kwargs: dict) -> discord.Embed: - embed = discord.Embed(title="Menu", colour=ghent_university_blue()) + def to_embed(self, **kwargs) -> discord.Embed: + day_dt: datetime = cast(datetime, kwargs.get("day_dt")) + weekday = int_to_weekday(day_dt.weekday()) + formatted_date = f"{leading('0', str(day_dt.day))}/{leading('0', str(day_dt.month))}/{day_dt.year}" + + embed = discord.Embed(title=f"Menu - {weekday} {formatted_date}", colour=ghent_university_blue()) return embed + + +def no_menu_found(day_dt: datetime) -> discord.Embed: + """Return a different embed if no menu could be found""" + embed = discord.Embed(title="Menu", colour=discord.Colour.red()) + embed.description = f"Unable to retrieve menu for {day_dt.strftime('%d/%m/%Y')}." + return embed diff --git a/didier/data/embeds/ufora/announcements.py b/didier/data/embeds/ufora/announcements.py index 9c3e6a8..59e8290 100644 --- a/didier/data/embeds/ufora/announcements.py +++ b/didier/data/embeds/ufora/announcements.py @@ -48,7 +48,7 @@ class UforaNotification(EmbedBaseModel): self.published_dt = self._published_datetime() self._published = self._get_published() - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: """Turn the notification into an embed""" embed = discord.Embed(title=self._title, colour=ghent_university_blue()) diff --git a/didier/data/embeds/urban_dictionary.py b/didier/data/embeds/urban_dictionary.py index 16aa9ca..3126fed 100644 --- a/didier/data/embeds/urban_dictionary.py +++ b/didier/data/embeds/urban_dictionary.py @@ -46,7 +46,7 @@ class Definition(EmbedPydantic): return string_utils.abbreviate(field, max_length=Limits.EMBED_FIELD_VALUE_LENGTH) @overrides - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: embed = discord.Embed(title="Urban Dictionary", colour=colours.urban_dictionary_green()) embed.add_field(name="Term", value=self.word, inline=True) diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index 44439c0..d29a29f 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -126,7 +126,7 @@ class WordleErrorEmbed(EmbedBaseModel): message: str @overrides - def to_embed(self, **kwargs: dict) -> discord.Embed: + def to_embed(self, **kwargs) -> discord.Embed: embed = discord.Embed(colour=discord.Colour.red(), title="Wordle") embed.description = self.message embed.set_footer(text=footer()) diff --git a/didier/utils/http/requests.py b/didier/utils/http/requests.py index f649701..ffcb498 100644 --- a/didier/utils/http/requests.py +++ b/didier/utils/http/requests.py @@ -2,7 +2,7 @@ import logging from contextlib import asynccontextmanager from typing import AsyncGenerator -from aiohttp import ClientResponse, ClientSession +from aiohttp import ClientResponse, ClientSession, ContentTypeError from didier.exceptions.http_exception import HTTPException @@ -18,13 +18,19 @@ def request_successful(response: ClientResponse) -> bool: @asynccontextmanager -async def ensure_get(http_session: ClientSession, endpoint: str) -> AsyncGenerator[dict, None]: +async def ensure_get( + http_session: ClientSession, endpoint: str, *, log_exceptions: bool = True +) -> AsyncGenerator[dict, None]: """Context manager that automatically raises an exception if a GET-request fails""" async with http_session.get(endpoint) as response: + try: + content = await response.json() + except ContentTypeError: + content = await response.text() + if not request_successful(response): - logger.error( - "Failed HTTP request to %s (status %s)\nResponse: %s", endpoint, response.status, await response.json() - ) + if log_exceptions: + logger.error("Failed HTTP request to %s (status %s)\nResponse: %s", endpoint, response.status, content) raise HTTPException(response.status) @@ -33,18 +39,29 @@ async def ensure_get(http_session: ClientSession, endpoint: str) -> AsyncGenerat @asynccontextmanager async def ensure_post( - http_session: ClientSession, endpoint: str, payload: dict, *, expect_return: bool = True + http_session: ClientSession, + endpoint: str, + payload: dict, + *, + log_exceptions: bool = True, + expect_return: bool = True ) -> AsyncGenerator[dict, None]: """Context manager that automatically raises an exception if a POST-request fails""" async with http_session.post(endpoint, data=payload) as response: if not request_successful(response): - logger.error( - "Failed HTTP request to %s (status %s)\nPayload: %s\nResponse: %s", - endpoint, - response.status, - payload, - await response.json(), - ) + try: + content = await response.json() + except ContentTypeError: + content = await response.text() + + if log_exceptions: + logger.error( + "Failed HTTP request to %s (status %s)\nPayload: %s\nResponse: %s", + endpoint, + response.status, + payload, + content, + ) raise HTTPException(response.status)