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

add static typecheck system to poetry #114

Merged
merged 10 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Code Style Check
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved

on: [pull_request]

jobs:
types:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true

- name: Install dependencies
run: poetry install --no-interaction

- name: Type with pyright
run: poetry run pyright uqcsbot
287 changes: 192 additions & 95 deletions poetry.lock

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,42 @@ pytest = "^7.3.1"
pytest-datafiles = "^3.0.0"
python-dotenv = "^1.0.0"
black = "^23.3.0"
pyright = "^1.1.316"
types-requests = "^2.30.0.0"
types-beautifulsoup4 = "^4.12.0.4"
types-python-dateutil = "^2.8.19.12"
types-pytz = "^2023.3.0.0"

[build-system]
requires = ["poetry-core>=1.3.0"]
build-backend = "poetry.core.masonry.api"

[tool.pyright]
strict = ["**"]
exclude = [
"**/__main__.py",
"**/advent.py",
"**/bot.py",
"**/cowsay.py",
"**/error_handler.py",
"**/events.py",
"**/gaming.py",
"**/haiku.py",
"**/holidays.py",
"**/hoogle.py",
"**/jobs_bulletin.py",
"**/member_counter.py",
"**/minecraft.py",
"**/remindme.py",
"**/snailrace.py",
"**/starboard.py",
"**/text.py",
"**/uptime.py",
"**/whatsdue.py",
"**/working_on.py",
"**/xkcd.py",
"**/utils/command_utils.py",
"**/utils/err_log_utils.py",
"**/utils/snailrace_utils.py",
"**/utils/uq_course_utils.py"
]
18 changes: 15 additions & 3 deletions uqcsbot/advent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@
from uqcsbot.bot import UQCSBot
from uqcsbot.models import AOCWinner
from uqcsbot.utils.command_utils import loading_status
from uqcsbot.utils.err_log_utils import FatalErrorWithLog

# Leaderboard API URL with placeholders for year and code.
LEADERBOARD_URL = "https://adventofcode.com/{year}/leaderboard/private/view/{code}.json"

# Session cookie (will expire in approx 30 days).
# See: https://github.com/UQComputingSociety/uqcsbot-discord/wiki/Tokens-and-Environment-Variables#aoc_session_id
SESSION_ID = os.environ.get("AOC_SESSION_ID")
SESSION_ID: str = ""

# UQCS leaderboard ID.
UQCS_LEADERBOARD = 989288

# Days in Advent of Code. List of numbers 1 to 25.
ADVENT_DAYS = list(range(1, 25 + 1))

# Puzzles are unlocked at midnight EST.
EST_TIMEZONE = timezone(timedelta(hours=-5))

Expand Down Expand Up @@ -334,7 +338,7 @@ def parse_arguments(self, argv: List[str]) -> Namespace:
def usage_error(message, *args, **kwargs):
raise ValueError(message)

parser.error = usage_error # type: ignore
parser.error = usage_error

args = parser.parse_args(argv)

Expand All @@ -343,7 +347,7 @@ def usage_error(message, *args, **kwargs):

return args

def get_leaderboard(self, year: int, code: int) -> Dict:
def get_leaderboard(self, year: int, code: int) -> Optional[Dict]:
"""
Returns a json dump of the leaderboard
"""
Expand Down Expand Up @@ -542,5 +546,13 @@ async def advent_winners(


async def setup(bot: UQCSBot):
if os.environ.get("AOC_SESSION_ID") is not None:
SESSION_ID = os.environ.get("AOC_SESSION_ID")
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved
else:
raise FatalErrorWithLog(
bot, "Unable to find AoC session ID. Not loading advent cog."
)

cog = Advent(bot)

await bot.add_cog(cog)
10 changes: 5 additions & 5 deletions uqcsbot/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ async def on_ready(self):
)

@commands.Cog.listener()
async def on_member_join(self, member):
async def on_member_join(self, member: discord.Member):
"""Member join listener"""
channel = member.guild.system_channel
if (channel := member.guild.system_channel) is None:
return
# On user joining, a system join message will appear in the system channel
# This should prevent the bot waving on a user message when #general is busy
async for msg in channel.history(limit=5):
Expand Down Expand Up @@ -83,7 +84,7 @@ def format_repo_message(self, repos: List[str]) -> str:
:param repos: list of strings of repo names
:return: a single string with a formatted message containing repo info for the given names
"""
repo_strings = []
repo_strings: List[str] = []
for potential_repo in repos:
repo_strings.append(self.find_repo(potential_repo))
return "".join(repo_strings)
Expand All @@ -98,8 +99,7 @@ async def repo_list(self, interaction: discord.Interaction):
+ self.format_repo_message(list(REPOS.keys()))
)

@repo_group.command(name="find")
@app_commands.describe(name="Name of the repo to find")
@repo_group.command(name="find", description="Name of the repo to find")
async def repo_find(self, interaction: discord.Interaction, name: str):
"""Finds a specific UQCS GitHub repository"""
await interaction.response.send_message(
Expand Down
8 changes: 4 additions & 4 deletions uqcsbot/bot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import os
from typing import List
from typing import List, Optional
import discord
from discord.ext import commands
from apscheduler.schedulers.asyncio import AsyncIOScheduler
Expand Down Expand Up @@ -35,9 +35,9 @@ async def admin_alert(
self,
title: str,
colour: discord.Colour,
description: str = None,
footer: str = None,
fields: List[tuple] = None,
description: Optional[str] = None,
footer: Optional[str] = None,
fields: Optional[List[tuple]] = None,
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved
fields_inline: bool = True,
):
"""Sends an alert to the admin channel for logging."""
Expand Down
2 changes: 1 addition & 1 deletion uqcsbot/cat.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def cat(self, interaction: discord.Interaction):
order = deque([pink, red, yellow, green, cyan, blue])
# randomly shifts starting colout
shift = randrange(0, 5)
for i in range(shift):
for _ in range(shift):
order.append(order.popleft())

cat = "\n".join(
Expand Down
6 changes: 3 additions & 3 deletions uqcsbot/cowsay.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def draw_cow(
"""

# Set the tongue if the cow is dead or if the tongue is set to True.
tongue = "U" if tongue or mood == "Dead" else " "
tongue_out = "U" if tongue or mood == "Dead" else " "

# Set the bubble connection based on whether the cow is thinking or
# speaking.
Expand All @@ -157,7 +157,7 @@ def draw_cow(
cow = f" {bubble_connect} ^__^\n"
cow += f" {bubble_connect} ({cow_eyes})\_______\n"
cow += f" (__)\ )\/\ \n"
cow += f" {tongue} ||----w |\n"
cow += f" {tongue_out} ||----w |\n"
cow += f" || ||\n"
return cow

Expand Down Expand Up @@ -242,7 +242,7 @@ def word_wrap(message: str, wrap: int) -> List[str]:
# As requested by the audience, you can manually break lines by
# adding "\n" anywhere in the message and it will be respected.
if "\\n" in word:
parts: str = word.split("\\n", 1)
parts: List[str] = word.split("\\n", 1)

# The `\n` is by itself, so start a new line.
if parts[0] == "" and parts[1] == "":
Expand Down
4 changes: 1 addition & 3 deletions uqcsbot/intros.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import discord
from discord.ext import commands

from uqcsbot.bot import UQCSBot


class Intros(commands.Cog):
CHANNEL_NAME = "intros"

def __init__(self, bot: UQCSBot):
def __init__(self, bot: commands.Bot):
self.bot = bot

@commands.Cog.listener()
Expand Down
66 changes: 36 additions & 30 deletions uqcsbot/models.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,56 @@
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import (
BigInteger,
Boolean,
Column,
Date,
DateTime,
Integer,
String,
Time,
)
from typing import Optional
from datetime import datetime

Base = declarative_base()


# Used for linking a message to a bot function.
# Previously used for the channel cog, currently unused.
class Message(Base):
__tablename__ = "messages"

id = Column("id", BigInteger, primary_key=True, nullable=False)
type = Column("type", String, nullable=False)
class Base(DeclarativeBase):
pass


class AOCWinner(Base):
__tablename__ = "aoc_winner"

id = Column("id", BigInteger, primary_key=True, nullable=False)
aoc_userid = Column("aoc_userid", Integer, nullable=False)
year = Column("year", Integer, nullable=False)
id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, nullable=False)
aoc_userid: Mapped[int] = mapped_column("aoc_userid", Integer, nullable=False)
year: Mapped[int] = mapped_column("year", Integer, nullable=False)


class MCWhitelist(Base):
__tablename__ = "mc_whitelisted"

mc_username = Column("mcuser", String, primary_key=True, nullable=False)
discord_id = Column("discordid", BigInteger, nullable=False)
admin_whitelisted = Column("adminwl", Boolean)
added_dt = Column("added_dt", DateTime, nullable=False)
mc_username: Mapped[str] = mapped_column(
"mcuser", String, primary_key=True, nullable=False
)
discord_id: Mapped[str] = mapped_column("discordid", BigInteger, nullable=False)
admin_whitelisted: Mapped[bool] = mapped_column("adminwl", Boolean)
added_dt: Mapped[datetime] = mapped_column("added_dt", DateTime, nullable=False)


class Reminders(Base):
__tablename__ = "reminders"

id = Column("id", BigInteger, primary_key=True, nullable=False)
user_id = Column("user_id", BigInteger, nullable=False)
channel_id = Column("channel_id", BigInteger, nullable=True)
time_created = Column("time_created", DateTime, nullable=False)
message = Column("message", String, nullable=False)
time = Column("time", Time, nullable=False)
start_date = Column("start_date", Date, nullable=False)
end_date = Column("end_date", Date, nullable=True)
week_frequency = Column("week_frequency", Integer, nullable=True)
id: Mapped[int] = mapped_column("id", BigInteger, primary_key=True, nullable=False)
user_id: Mapped[int] = mapped_column("user_id", BigInteger, nullable=False)
channel_id: Mapped[Optional[int]] = mapped_column(
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved
"channel_id", BigInteger, nullable=True
)
time_created: Mapped[int] = mapped_column("time_created", DateTime, nullable=False)
message: Mapped[str] = mapped_column("message", String, nullable=False)
time = mapped_column("time", Time, nullable=False)
start_date = mapped_column("start_date", Date, nullable=False)
end_date = mapped_column("end_date", Date, nullable=True)
andrewj-brown marked this conversation as resolved.
Show resolved Hide resolved
week_frequency: Mapped[Optional[int]] = mapped_column(
"week_frequency", Integer, nullable=True
)


class Starboard(Base):
Expand All @@ -61,6 +61,12 @@ class Starboard(Base):
# recv == null implies deleted recv message.
# recv_location == null implies deleted recv channel. recv should also be null.
# sent == null implies blacklisted recv message.
recv = Column("recv", BigInteger, primary_key=True, nullable=True)
recv_location = Column("recv_location", BigInteger, nullable=True, unique=False)
sent = Column("sent", BigInteger, primary_key=True, nullable=True, unique=True)
recv: Mapped[Optional[int]] = mapped_column(
"recv", BigInteger, primary_key=True, nullable=True
)
recv_location: Mapped[Optional[int]] = mapped_column(
"recv_location", BigInteger, nullable=True, unique=False
)
sent: Mapped[Optional[int]] = mapped_column(
"sent", BigInteger, primary_key=True, nullable=True, unique=True
)