From 86b7fe1c0738a4b5b3f8162774906003f3a752a4 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 30 Jun 2023 23:06:47 +1000 Subject: [PATCH 1/2] Add Pyright instructions to the readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 2a85e315..092833ae 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,14 @@ We use an automated code formatter called [Black](https://black.readthedocs.io/) poetry run black uqcsbot ``` +## Static Type Checks + +We use [Pyright](https://github.com/microsoft/pyright) to perform static type checks; which all commits should pass. The exception list is within `pyproject.toml` is for legacy code only, as we hope that all new cogs can be made to pass. To run Pyright, run from the root of the repo: + +```bash +poetry run pyright uqcsbot +``` + ## Development Resources If this is your first time working on an open source project, we're here to walk you through every step of the way. From eea0fd5a375a962b2d5763a80864b04d8bd21bda Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 5 Jul 2024 18:16:25 +1000 Subject: [PATCH 2/2] Updated typing to fit apschedler v4 --- uqcsbot/advent.py | 27 +++++++++++++++------------ uqcsbot/bot.py | 21 +++++++++++++-------- uqcsbot/events.py | 10 ++++++---- uqcsbot/holidays.py | 9 +++++---- uqcsbot/remindme.py | 40 +++++++++++++++++++++------------------- uqcsbot/working_on.py | 3 ++- uqcsbot/yelling.py | 3 ++- 7 files changed, 64 insertions(+), 49 deletions(-) diff --git a/uqcsbot/advent.py b/uqcsbot/advent.py index 5f53d60f..987b4bfd 100644 --- a/uqcsbot/advent.py +++ b/uqcsbot/advent.py @@ -6,6 +6,7 @@ import requests from requests.exceptions import RequestException from sqlalchemy.sql.expression import and_ +from apscheduler.triggers.cron import CronTrigger import discord from discord import app_commands @@ -69,7 +70,7 @@ member.times[day].get(2, MAXIMUM_TIME_FOR_STAR), member.times[day].get(1, MAXIMUM_TIME_FOR_STAR), ), - "Total Time": lambda member, dat: ( + "Total Time": lambda member, day: ( member.get_total_time(default=MAXIMUM_TIME_FOR_STAR), -member.star_total, ), @@ -171,20 +172,22 @@ def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( self.reminder_released, - trigger="cron", - timezone="Australia/Brisbane", - hour=15, - day="1-25", - month=12, + trigger=CronTrigger( + timezone=bot.BOT_TIMEZONE, + hour=15, + day="1-25", + month=12, + ), ) self.bot.schedule_task( self.reminder_fifteen_minutes, - trigger="cron", - timezone="Australia/Brisbane", - hour=14, - minute=45, - day="1-25", - month=12, + trigger=CronTrigger( + timezone=bot.BOT_TIMEZONE, + hour=14, + minute=45, + day="1-25", + month=12, + ), ) # A dictionary from a year to the list of members diff --git a/uqcsbot/bot.py b/uqcsbot/bot.py index bc01bd3d..6cc56c92 100644 --- a/uqcsbot/bot.py +++ b/uqcsbot/bot.py @@ -1,10 +1,12 @@ import logging import os -from typing import List, Optional, Tuple, Any, Callable, Coroutine +from typing import List, Optional, Tuple, Any, Callable import discord from discord.ext import commands -from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler import AsyncScheduler +from apscheduler.abc import Trigger +from asyncio import run from sqlalchemy.engine import Engine from sqlalchemy.orm import sessionmaker from datetime import datetime @@ -23,7 +25,7 @@ class UQCSBot(commands.Bot): def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) - self._scheduler = AsyncIOScheduler() + self._scheduler = AsyncScheduler() self.start_time = datetime.now() # Important channel names & constants go here @@ -36,10 +38,13 @@ def __init__(self, *args: Any, **kwargs: Any): self.uqcs_server: discord.Guild def schedule_task( - self, func: Callable[..., Coroutine[Any, Any, None]], *args: Any, **kwargs: Any + self, func: Callable[..., Any], trigger: Trigger, *args: Any, **kwargs: Any ): - """Schedule a function to be run at a later time. A wrapper for apscheduler add_job.""" - self._scheduler.add_job(func, *args, **kwargs) + """Schedule a function to be run at a later time. A wrapper for apscheduler add_schedule.""" + # The apschedule library has an `Unknown` type within their definition of the type of `add_schedule` + run( + self._scheduler.add_schedule(func, trigger, *args, **kwargs) + ) # pyright: ignore[reportUnknownMemberType] def set_db_engine(self, db_engine: Engine): """Creates a sessionmaker from the provided database engine which can be called from commands.""" @@ -81,7 +86,7 @@ async def admin_alert( # Web server binds to port 8080. This is a basic template to ensure # that Azure has something for a health check. async def web_server(self): - def handle(request): + async def handle(_: web.Request): return web.Response(text="UQCSbot is running") app = web.Application() @@ -93,7 +98,7 @@ def handle(request): async def on_ready(self): """Once the bot is loaded and has connected, run these commands first.""" - self._scheduler.start() + await self._scheduler.start_in_background() if (user := self.user) is None: raise RuntimeError("Ready... but not logged in!") diff --git a/uqcsbot/events.py b/uqcsbot/events.py index dd10e6d7..5e868d04 100644 --- a/uqcsbot/events.py +++ b/uqcsbot/events.py @@ -2,6 +2,7 @@ from calendar import day_abbr, month_abbr, month_name from datetime import date, datetime, timedelta from typing import List, Optional, Tuple, Dict +from apscheduler.triggers.cron import CronTrigger import discord from discord import app_commands @@ -195,10 +196,11 @@ def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( self.scheduled_message, - trigger="cron", - hour=9, - day_of_week="mon", - timezone="Australia/Brisbane", + trigger=CronTrigger( + hour=9, + day_of_week="mon", + timezone="Australia/Brisbane", + ), ) async def scheduled_message(self): diff --git a/uqcsbot/holidays.py b/uqcsbot/holidays.py index a31618c5..22893473 100644 --- a/uqcsbot/holidays.py +++ b/uqcsbot/holidays.py @@ -1,3 +1,4 @@ +from apscheduler.triggers.cron import CronTrigger from bs4 import BeautifulSoup import csv from datetime import datetime @@ -114,10 +115,10 @@ def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( self.holiday, - trigger="cron", - hour=9, - minute=0, - timezone="Australia/Brisbane", + trigger=CronTrigger( + hour=9, + timezone=bot.BOT_TIMEZONE, + ), ) async def holiday(self): diff --git a/uqcsbot/remindme.py b/uqcsbot/remindme.py index 3b545792..0afe2c11 100644 --- a/uqcsbot/remindme.py +++ b/uqcsbot/remindme.py @@ -1,4 +1,5 @@ import datetime as dt +from apscheduler.triggers.cron import CronTrigger import discord from discord import app_commands from discord.ext import commands @@ -6,6 +7,7 @@ import logging from typing import List, NamedTuple, Optional, Union from zoneinfo import ZoneInfo +from asyncio import run from uqcsbot.bot import UQCSBot from uqcsbot.models import Reminders @@ -257,24 +259,23 @@ def _schedule_reminder(self, reminder: Reminder): and end_datetime != None and end_datetime < dt.datetime.now() ): - self.bot.schedule_task( - partial(self._process_reminder, reminder), misfire_grace_time=None - ) + run(self._process_reminder(reminder)) # otherwise, reminder datetime is in the future so we can schedule it if reminder.week_frequency == None or start_datetime > dt.datetime.now(): # one-time reminder OR first occurrence of recurring reminder, so schedule for start_date return self.bot.schedule_task( partial(self._process_reminder, reminder), - trigger="cron", - timezone="Australia/Brisbane", + trigger=CronTrigger( + timezone=self.bot.BOT_TIMEZONE, + year=start_date.year, + month=start_date.month, + day=start_date.day, + hour=time.hour, + minute=time.minute, + second=time.second, + ), misfire_grace_time=None, - year=start_date.year, - month=start_date.month, - day=start_date.day, - hour=time.hour, - minute=time.minute, - second=time.second, ) # non-first occurrence of recurring reminder, schedule next occurrence based on week_frequency @@ -305,15 +306,16 @@ def _schedule_reminder(self, reminder: Reminder): self.bot.schedule_task( partial(self._process_reminder, reminder), - trigger="cron", - timezone="Australia/Brisbane", + trigger=CronTrigger( + timezone=self.bot.BOT_TIMEZONE, + year=year, + month=month, + day=day, + hour=time.hour, + minute=time.minute, + second=time.second, + ), misfire_grace_time=None, - year=year, - month=month, - day=day, - hour=time.hour, - minute=time.minute, - second=time.second, ) async def _process_reminder(self, reminder: Reminder): diff --git a/uqcsbot/working_on.py b/uqcsbot/working_on.py index 8ebfea12..b15c46f2 100644 --- a/uqcsbot/working_on.py +++ b/uqcsbot/working_on.py @@ -1,5 +1,6 @@ import logging from random import choice +from apscheduler.triggers.cron import CronTrigger import discord from discord.ext import commands @@ -13,7 +14,7 @@ class WorkingOn(commands.Cog): def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( - self.workingon, trigger="cron", hour=17, timezone="Australia/Brisbane" + self.workingon, trigger=CronTrigger(hour=17, timezone=bot.BOT_TIMEZONE) ) async def workingon(self): diff --git a/uqcsbot/yelling.py b/uqcsbot/yelling.py index 90660be0..fd71b96f 100644 --- a/uqcsbot/yelling.py +++ b/uqcsbot/yelling.py @@ -10,6 +10,7 @@ from datetime import timedelta from functools import wraps +from apscheduler.triggers.cron import CronTrigger def yelling_exemptor(input_args: List[str] = ["text"]) -> Callable[..., Any]: @@ -64,7 +65,7 @@ class Yelling(commands.Cog): def __init__(self, bot: UQCSBot): self.bot = bot self.bot.schedule_task( - self.clear_bans, trigger="cron", hour=17, timezone="Australia/Brisbane" + self.clear_bans, trigger=CronTrigger(hour=17, timezone=bot.BOT_TIMEZONE) ) @commands.Cog.listener()