Feat: Update utils to use new models.
build_develop / build (push) Successful in 35s

Address many pylance smells. Ignoring some I don't know how to handle,
added some TODOs for handling others later.
This commit is contained in:
2025-07-16 12:02:56 -04:00
parent abc2950b21
commit 44baf01a51
+103 -84
View File
@@ -1,7 +1,7 @@
"""File contains util functions.""" """File contains util functions."""
import calendar import calendar
import os import pathlib
from collections.abc import Sequence from collections.abc import Sequence
from datetime import UTC, datetime from datetime import UTC, datetime
from enum import StrEnum from enum import StrEnum
@@ -9,17 +9,12 @@ from pathlib import Path
import aiofiles import aiofiles
import nextcord import nextcord
from sqlalchemy import select, true, update from sqlalchemy import select, update
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlmodel import col
from dakiny_bot.database.sql_data_classes import ( from dakiny_bot.database.db import engine
CouncilMembers, from dakiny_bot.database.models import CouncilMember, Message, Points, PointsLimit, Vote
Messages,
Points,
PointsLimit,
Votes,
engine,
)
from dakiny_bot.logger import dakiny_logger from dakiny_bot.logger import dakiny_logger
from .dakiny_settings import settings from .dakiny_settings import settings
@@ -35,7 +30,9 @@ VoteType = StrEnum("VoteType", ["COUNCIL", "DISCORDMANAGER", "CLANLEADER"])
PointType = StrEnum("PointType", ["CLAN", "EVENT"]) PointType = StrEnum("PointType", ["CLAN", "EVENT"])
def get_datetime(month_day: str) -> datetime: # TODO(imcompiling): resolve returning None from this function
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
def get_datetime(month_day: str) -> datetime: # pyright: ignore[reportReturnType]
""" """
Takes in a string month and day and returns a datetime object of that Month/Day this year. Takes in a string month and day and returns a datetime object of that Month/Day this year.
@@ -129,7 +126,9 @@ def get_quarter_cycle() -> int:
return cycle return cycle
dakiny_logger.debug("Quarter cycle is None.") dakiny_logger.debug("Quarter cycle is None.")
return None # TODO(imcompiling) Add None to return signature or find better return value
# https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
return None # pyright: ignore[reportReturnType]
def build_votes_str(guild: nextcord.Guild) -> str: def build_votes_str(guild: nextcord.Guild) -> str:
@@ -147,19 +146,20 @@ def build_votes_str(guild: nextcord.Guild) -> str:
election_name = get_election_name() election_name = get_election_name()
with Session(engine) as session: with Session(engine) as session:
stmt = select(CouncilMembers).where(CouncilMembers.ElectionName == election_name) stmt = select(CouncilMember).where(col(CouncilMember.election_name == election_name))
for nominee in sorted( for nominee in sorted(
session.scalars(stmt), session.scalars(stmt),
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(), # Unsure how to None check in the lambda.
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
): ):
votes[nominee.MemberId] = 0 votes[nominee.member_id] = 0
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.COUNCIL) stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.COUNCIL))
for vote in session.scalars(stmt): for vote in session.scalars(stmt):
if vote.VotedForId is not None: if vote.voted_for_id is not None:
votes[vote.VotedForId] += 1 votes[vote.voted_for_id] += 1
votes_str: str = "====================" votes_str: str = "===================="
votes_str += user_vote_str(votes) votes_str += user_vote_str(votes)
@@ -184,46 +184,52 @@ def build_highcouncil_votes_str(guild: nextcord.Guild) -> str:
with Session(engine) as session: with Session(engine) as session:
stmt = ( stmt = (
select(CouncilMembers) select(CouncilMember)
.where(CouncilMembers.ElectionName == election_name) .where(col(CouncilMember.election_name == election_name))
.filter(CouncilMembers.ClanLeaderSpeechId.is_not(None)) .filter(col(CouncilMember.clan_leader_speech_id is not None))
) )
for nominee in sorted( for nominee in sorted(
session.scalars(stmt), session.scalars(stmt),
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(), # Unsure how to None check in the lambda.
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
): ):
cl_votes[nominee.MemberId] = 0 cl_votes[nominee.member_id] = 0
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.CLANLEADER) stmt = select(Vote).where(col(Vote.election_name == election_name)).where(col(Vote.type == VoteType.CLANLEADER))
for vote in session.scalars(stmt): for vote in session.scalars(stmt):
if vote.VotedForId is not None: if vote.voted_for_id is not None:
cl_votes[vote.VotedForId] += 1 cl_votes[vote.voted_for_id] += 1
stmt = ( stmt = (
select(CouncilMembers) select(CouncilMember)
.where(CouncilMembers.ElectionName == election_name) .where(col(CouncilMember.election_name == election_name))
.filter(CouncilMembers.DiscordManagerSpeechId.is_not(None)) .filter(col(CouncilMember.discord_manager_speech_id is not None))
) )
for nominee in sorted( for nominee in sorted(
session.scalars(stmt), session.scalars(stmt),
key=lambda nominee: guild.get_member(nominee.MemberId).display_name.lower(), # Unsure how to None check in the lambda.
key=lambda nominee: guild.get_member(nominee.member_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
): ):
dm_votes[nominee.MemberId] = 0 dm_votes[nominee.member_id] = 0
stmt = select(Votes).where(Votes.ElectionName == election_name).where(Votes.Type == VoteType.DISCORDMANAGER) stmt = (
select(Vote)
.where(col(Vote.election_name == election_name))
.where(col(Vote.type == VoteType.DISCORDMANAGER))
)
for vote in session.scalars(stmt): for vote in session.scalars(stmt):
if vote.VotedForId is not None: if vote.voted_for_id is not None:
dm_votes[vote.VotedForId] += 1 dm_votes[vote.voted_for_id] += 1
# build the votes string # build the votes string
votes_str: str = "Clan Leader Votes\n====================" votes_str: str = "Clan Leader Vote\n===================="
votes_str += user_vote_str(cl_votes) votes_str += user_vote_str(cl_votes)
votes_str += "\n\nDiscord Manager Votes\n====================" votes_str += "\n\nDiscord Manager Vote\n===================="
votes_str += user_vote_str(dm_votes) votes_str += user_vote_str(dm_votes)
return votes_str return votes_str
@@ -317,7 +323,7 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
quarter_name = get_quarter_name() quarter_name = get_quarter_name()
with Session(engine) as session: with Session(engine) as session:
for user in users: for user in users:
stmt = select(Points).where(Points.UserId == user).where(Points.QuarterName == quarter_name) stmt = select(Points).where(col(Points.user_id == user)).where(col(Points.quarter_name == quarter_name))
points_obj = session.scalar(stmt) points_obj = session.scalar(stmt)
add_points = get_allowed_points(session, point_type, user, add_points) add_points = get_allowed_points(session, point_type, user, add_points)
@@ -325,16 +331,16 @@ def record_points(users: Sequence[str], add_points: int, point_type: PointType,
add_event_points = add_points if point_type == PointType.EVENT else 0 add_event_points = add_points if point_type == PointType.EVENT else 0
if points_obj is None: if points_obj is None:
new_points = Points( new_points = Points(
QuarterName=quarter_name, quarter_name=quarter_name,
UserId=user, user_id=user,
DiscordMember=is_member, discord_member=is_member,
EventPoints=add_event_points, event_points=add_event_points,
ClanPoints=add_clan_points, clan_points=add_clan_points,
) )
session.add(new_points) session.add(new_points)
else: else:
points_obj.EventPoints += add_event_points points_obj.event_points += add_event_points
points_obj.ClanPoints += add_clan_points points_obj.clan_points += add_clan_points
session.commit() session.commit()
@@ -364,7 +370,7 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
if point_type == PointType.EVENT if point_type == PointType.EVENT
else settings.point_settings.clan_points_weekly_limit else settings.point_settings.clan_points_weekly_limit
) )
stmt = select(PointsLimit).where(PointsLimit.UserId == user).where(PointsLimit.Week == this_week) stmt = select(PointsLimit).where(col(PointsLimit.user_id == user)).where(col(PointsLimit.week == this_week))
# if under limit, can add up to however many points would make the current limit equal to the # if under limit, can add up to however many points would make the current limit equal to the
# allowed limit. # allowed limit.
@@ -372,16 +378,16 @@ def get_allowed_points(session: Session, point_type: PointType, user: str, point
if limit_obj is None: if limit_obj is None:
clan_points = points if point_type == PointType.CLAN else 0 clan_points = points if point_type == PointType.CLAN else 0
event_points = points if point_type == PointType.EVENT else 0 event_points = points if point_type == PointType.EVENT else 0
new_limit = PointsLimit(UserId=user, Week=this_week, EventPoints=event_points, ClanPoints=clan_points) new_limit = PointsLimit(user_id=user, week=this_week, event_points=event_points, clan_points=clan_points)
add_points = points add_points = points
session.add(new_limit) session.add(new_limit)
else: else:
curr_points = limit_obj.EventPoints if point_type == PointType.EVENT else limit_obj.ClanPoints curr_points = limit_obj.event_points if point_type == PointType.EVENT else limit_obj.clan_points
add_points = points if curr_points + points <= points_limit else (max(0, points_limit - curr_points)) add_points = points if curr_points + points <= points_limit else (max(0, points_limit - curr_points))
if point_type == PointType.EVENT: if point_type == PointType.EVENT:
limit_obj.EventPoints += add_points limit_obj.event_points += add_points
else: else:
limit_obj.ClanPoints += add_points limit_obj.clan_points += add_points
session.commit() session.commit()
@@ -410,8 +416,8 @@ async def update_clan_messages(guild: nextcord.Guild, channel_updates: Sequence[
dir_path = f"{MESSAGES_DIR}/{dir_name}" dir_path = f"{MESSAGES_DIR}/{dir_name}"
channel = await get_clan_message_channel(session, dir_name, guild) channel = await get_clan_message_channel(session, dir_name, guild)
sorted_files = sorted( sorted_files = sorted(
os.listdir(dir_path), pathlib.Path(dir_path).iterdir(),
key=lambda filename: int(filename.split(".")[0]), key=lambda file: int(file.name.split(".")[0]),
) )
for file_name in sorted_files: for file_name in sorted_files:
@@ -440,41 +446,49 @@ async def get_clan_message_channel(session: Session, channel_name: str, guild: n
Returns: Returns:
nextcord.TextChannel: Channel object with name channel_name. nextcord.TextChannel: Channel object with name channel_name.
""" """
stmt = select(Messages.Id).where(Messages.Path == channel_name) stmt = select(col(Message.id)).where(col(Message.path == channel_name))
channel_id = session.scalar(stmt) channel_id = session.scalar(stmt)
ret_channel = None
if channel_id is None: if channel_id is None:
ret_channel = None
# channel is not tracked. see if it exists # channel is not tracked. see if it exists
channels = await guild.fetch_channels() channels = await guild.fetch_channels()
for channel in channels: for channel in channels:
if channel.name == channel_name and channel.__class__ is nextcord.TextChannel: if channel.name == channel_name and isinstance(channel, nextcord.TextChannel):
ret_channel = channel ret_channel = channel
if ret_channel is None: if ret_channel is None:
# channel doesn't exist, we must create it # channel doesn't exist, we must create it
ret_channel = await guild.create_text_channel( ret_channel = await guild.create_text_channel(
channel_name, channel_name,
reason="Creating channel as required for by design of Dakiny-Messages repo.", reason="Creating channel as required for by design of Dakiny-Message repo.",
) )
# record channel information # record channel information
new_item = Messages(Path=channel_name, Id=ret_channel.id) new_item = Message(path=channel_name, id=ret_channel.id)
session.add(new_item) session.add(new_item)
session.commit() session.commit()
return ret_channel
try: else:
return await guild.fetch_channel(channel_id) try:
except nextcord.NotFound: ret_channel = await guild.fetch_channel(channel_id)
# the channel was deleted for some reason if not isinstance(ret_channel, nextcord.TextChannel):
ret_channel = await guild.create_text_channel( dakiny_logger.warning(
channel_name, "Channel %s is not a text channel. Does not work for clan messages.", ret_channel.name
reason="Creating channel as required for by design of Dakiny-Messages repo.", )
) # TODO(imcompiling) Figure out how to handle returning None here. Maybe delete channel and remake it?
stmt = update(Messages).where(Messages.Path == channel_name).values(Id=ret_channel.id) # https://gitea.cheezers.org/imcompiling/dakiny-bot/issues/102
session.execute(stmt) return None # pyright: ignore[reportReturnType]
session.commit() except nextcord.NotFound:
return ret_channel # the channel was deleted for some reason
ret_channel = await guild.create_text_channel(
channel_name,
reason="Creating channel as required for by design of Dakiny-Message repo.",
)
stmt = update(Message).where(col(Message.path == channel_name)).values(id=ret_channel.id)
session.execute(stmt)
session.commit()
return ret_channel
async def edit_clan_message( async def edit_clan_message(
@@ -496,21 +510,21 @@ async def edit_clan_message(
message_name (str): Name of the message. Corresponds to the file that contains message content. message_name (str): Name of the message. Corresponds to the file that contains message content.
message_content (str): Content of the message to edit/send. message_content (str): Content of the message to edit/send.
""" """
stmt = select(Messages.Id).where(Messages.Path == message_name) stmt = select(col(Message.id)).where(col(Message.path == message_name))
msg_id = session.scalar(stmt) msg_id = session.scalar(stmt)
if msg_id is None: if msg_id is None:
# don't have a message for this file # don't have a message for this file
msg = await channel.send(message_content) msg = await channel.send(message_content)
new_message = Messages(Path=message_name, Id=msg.id) new_message = Message(path=message_name, id=msg.id)
session.add(new_message) session.add(new_message)
session.commit() session.commit()
else: else:
try: try:
msg: nextcord.Message = await channel.fetch_message(msg_id) msg: nextcord.Message = await channel.fetch_message(msg_id)
await msg.edit(message_content) await msg.edit(content=message_content)
except nextcord.NotFound: except nextcord.NotFound:
msg = await channel.send(message_content) msg = await channel.send(message_content)
stmt = update(Messages).where(Messages.Path == message_name).values(Id=msg.id) stmt = update(Message).where(col(Message.path == message_name)).values(id=msg.id)
session.execute(stmt) session.execute(stmt)
session.commit() session.commit()
@@ -522,30 +536,35 @@ async def edit_clan_council_info(guild: nextcord.Guild) -> None:
Args: Args:
guild (nextcord.Guild): Guild object that to use to get channel. guild (nextcord.Guild): Guild object that to use to get channel.
""" """
clan_info_channel: nextcord.TextChannel = await guild.fetch_channel(settings.channel_ids.clan_info) clan_info_channel = await guild.fetch_channel(settings.channel_ids.clan_info)
if not isinstance(clan_info_channel, nextcord.TextChannel):
return
election_name = get_election_name() election_name = get_election_name()
with Session(engine) as session: with Session(engine) as session:
stmt = ( stmt = (
select(CouncilMembers.MemberId) select(col(CouncilMember.member_id))
.where(CouncilMembers.ElectionName == election_name) .where(col(CouncilMember.election_name == election_name))
.where(CouncilMembers.DiscordManager == true()) .where(col(CouncilMember.discord_manager is True))
) )
discord_manager = session.scalar(stmt) discord_manager = session.scalar(stmt)
stmt = ( stmt = (
select(CouncilMembers.MemberId) select(col(CouncilMember.member_id))
.where(CouncilMembers.ElectionName == election_name) .where(col(CouncilMember.election_name == election_name))
.where(CouncilMembers.ClanLeader == true()) .where(col(CouncilMember.clan_leader is True))
) )
clan_leader = session.scalar(stmt) clan_leader = session.scalar(stmt)
stmt = ( stmt = (
select(CouncilMembers.MemberId) select(col(CouncilMember.member_id))
.where(CouncilMembers.ElectionName == election_name) .where(col(CouncilMember.election_name == election_name))
.where(CouncilMembers.Elected == true()) .where(col(CouncilMember.elected is True))
) )
council_member_ids = sorted( council_member_ids = sorted(
session.scalars(stmt), key=lambda user_id: guild.get_member(user_id).display_name.lower() session.scalars(stmt),
# Unsure how to None check in the lambda.
key=lambda user_id: guild.get_member(user_id).display_name.lower(), # pyright: ignore[reportOptionalMemberAccess]
) )
council_members_strs = [ council_members_strs = [