diff --git a/classes/election_cycle.py b/classes/election_cycle.py index 2a8e49d..461ebd2 100644 --- a/classes/election_cycle.py +++ b/classes/election_cycle.py @@ -1,9 +1,8 @@ -""" -Contains the election cycle class -""" +"""Contains the election cycle class.""" import collections from datetime import UTC, datetime, timedelta +from pathlib import Path import nextcord from classes.nominate_highcouncil_views import NominateHighCouncilView @@ -29,9 +28,13 @@ from utils import ( get_election_name, ) +REQUIRED_VOTES_TO_BE_ELECTED = 3 + class ElectionCycle: """ + ElectionCycle class. + This class represents all the dates required for an election cycle along with all the functions needed to operate the cycle. """ @@ -67,7 +70,7 @@ class ElectionCycle: list[str]: A list of message contents in the correct order. Must be a string because the message is too long. """ - with open(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/election_announcement.txt", encoding="utf8").open() as file: message = file.read() next_election = get_datetime( @@ -95,7 +98,7 @@ class ElectionCycle: Returns: str: Message to send. """ - with open(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/highcouncil_election.txt", encoding="utf8").open() as file: message = file.read() return message.format( @@ -113,7 +116,7 @@ class ElectionCycle: Returns: str: Message to send. """ - with open(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/highcouncil_vote.txt", encoding="utf8").open() as file: message = file.read() return message.format( @@ -128,7 +131,7 @@ class ElectionCycle: Returns: str: Message to send """ - with open(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8") as file: + with Path(f"{TEMPLATES_DIR}/vote_start.txt", encoding="utf8").open() as file: message = file.read() vote_end: datetime = get_datetime(self.config.election_cycles[self.cycle].voting_end) @@ -139,9 +142,7 @@ class ElectionCycle: ) async def check_date(self) -> None: - """ - Function checks current date to see if something needs to be run. - """ + """Function checks current date to see if something needs to be run.""" today = datetime.now(tz=UTC).date() # check all dates if action is needed @@ -159,9 +160,7 @@ class ElectionCycle: await self.term_start_action() async def election_start_action(self) -> None: - """ - Performs required actions for when the election cycle starts. - """ + """Performs required actions for when the election cycle starts.""" # create new channel for election parent: nextcord.TextChannel = self.guild.get_channel(self.config.params.voting_parent_id) quarter_name: str = get_election_name(self.config) @@ -193,6 +192,8 @@ class ElectionCycle: async def voting_start_action(self) -> None: """ + Actions performed when starting a vote. + Sends message informing members that voting has started. Removes write permission from the general clan member role. """ @@ -217,9 +218,7 @@ class ElectionCycle: await nominate_msg.edit(view=None) async def voting_end_action(self) -> None: - """ - Performs required actions for when voting time has ended. - """ + """Performs required actions for when voting time has ended.""" message = self.build_highcouncil_election_announce() council_elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) @@ -250,7 +249,7 @@ class ElectionCycle: vote_count = collections.Counter(session.scalars(stmt)) for user_id, votes in vote_count.items(): - if votes >= 3: + if votes >= REQUIRED_VOTES_TO_BE_ELECTED: stmt = ( select(CouncilMembers) .where(CouncilMembers.ElectionCycle == election_name) @@ -268,9 +267,7 @@ class ElectionCycle: ) async def highcouncil_election_start_action(self) -> None: - """ - Performs required actions for when the highcouncil is elected. - """ + """Performs required actions for when the highcouncil is elected.""" message = self.build_highcouncil_vote_start() election_thread: nextcord.Thread = self.guild.get_thread(self.get_election_info().ThreadId) await election_thread.send(message) @@ -292,9 +289,7 @@ class ElectionCycle: await nomination_msg.edit(view=None) async def term_start_action(self) -> None: - """ - Marks the election as over. - """ + """Marks the election as over.""" self.election_over = True elect_role: nextcord.Role = self.guild.get_role(self.config.params.clan_council_elect_role_id) @@ -394,9 +389,7 @@ class ElectionCycle: return session.scalar(stmt) async def election_reminder(self) -> None: - """ - Sends a message to general chat reminder clan about election. - """ + """Sends a message to general chat reminder clan about election.""" general: nextcord.TextChannel = self.guild.get_channel(self.config.params.general_channel_id) message = f"Reminder to <@&{self.config.params.clan_member_role_id}>! Clan elections are happening! Head over " diff --git a/classes/nominate_highcouncil_views.py b/classes/nominate_highcouncil_views.py index 49e829f..9fdbcc8 100644 --- a/classes/nominate_highcouncil_views.py +++ b/classes/nominate_highcouncil_views.py @@ -1,14 +1,11 @@ -""" -Contains various views to be used in the high council nomination process. -""" +"""Contains various views to be used in the high council nomination process.""" import nextcord +from config import DakinyConfig +from database_setup.sql_data_classes import CouncilMembers, engine from nextcord.interactions import Interaction from sqlalchemy import select from sqlalchemy.orm import Session - -from config import DakinyConfig -from database_setup.sql_data_classes import CouncilMembers, engine from utils import ( NOMINEE_SPLITTER, decorate_cl_nomination, @@ -19,14 +16,16 @@ from utils import ( class NominateDMModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -79,14 +78,16 @@ class NominateDMModal(nextcord.ui.Modal): class NominateCLModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -138,9 +139,7 @@ class NominateCLModal(nextcord.ui.Modal): class NominateHighCouncilView(nextcord.ui.View): - """ - The buttons to press to start your High Council nomination. - """ + """The buttons to press to start your High Council nomination.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -152,8 +151,10 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-dm", ) - async def nominate_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button for nominating oneself for Discord Manager. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. @@ -195,8 +196,10 @@ class NominateHighCouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate-cl", ) - async def nominate_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button for nominating oneself for Clan Leader. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. diff --git a/classes/nominate_views.py b/classes/nominate_views.py index 8211288..b6d8578 100644 --- a/classes/nominate_views.py +++ b/classes/nominate_views.py @@ -1,27 +1,26 @@ -""" -Contains various views to be used in the nomination process. -""" +"""Contains various views to be used in the nomination process.""" import nextcord +from config import DakinyConfig +from database_setup.sql_data_classes import CouncilMembers, engine from nextcord.interactions import Interaction from sqlalchemy import select from sqlalchemy.orm import Session - -from config import DakinyConfig -from database_setup.sql_data_classes import CouncilMembers, engine from utils import NOMINEE_SPLITTER, decorate_nomination, get_election_name class NominateModal(nextcord.ui.Modal): """ - Modal for nominating oneself. User writes a speech and then the callback sends the speech into the correct channel. + Modal for nominating oneself. + + User writes a speech and then the callback sends the speech into the correct channel. """ def __init__( self, config: DakinyConfig, usr_id: int, - usr_nominated: bool, + usr_nominated: bool, # noqa: FBT001 speech_content: str, ): super().__init__( @@ -78,9 +77,7 @@ class NominateModal(nextcord.ui.Modal): class NominateView(nextcord.ui.View): - """ - The button to press to start your nomination. - """ + """The button to press to start your nomination.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -92,8 +89,10 @@ class NominateView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="nominate", ) - async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def nominate(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to nominate onself for Clan Council. + Gathers previous information about the user if they have already nominated themselves, then launches the nomination modal to get their speech. diff --git a/classes/pyproject.toml b/classes/pyproject.toml index e3c5aa1..357e23b 100644 --- a/classes/pyproject.toml +++ b/classes/pyproject.toml @@ -2,6 +2,9 @@ # Extend the `pyproject.toml` file in the parent directory... extend = "../pyproject.toml" +# Exclude a variety of commonly ignored directories. +exclude = ["gitea_webhook.py"] + [tool.ruff.lint] # ignore implicit-namespace-package in this directory. ignore = ["INP001"] diff --git a/classes/voting_highcouncil_views.py b/classes/voting_highcouncil_views.py index a0e9350..f35677c 100644 --- a/classes/voting_highcouncil_views.py +++ b/classes/voting_highcouncil_views.py @@ -1,14 +1,6 @@ -""" -Contains various views to be used while voting for high council. Controls the logic when performing the vote. -""" - +"""Contains various views to be used while voting for high council. Controls the logic when performing the vote.""" import nextcord -from nextcord import SelectOption -from nextcord.interactions import Interaction -from sqlalchemy import exc, select, update -from sqlalchemy.orm import Session - from config import DakinyConfig from database_setup.sql_data_classes import ( CouncilMembers, @@ -16,10 +8,16 @@ from database_setup.sql_data_classes import ( Votes, engine, ) +from nextcord import SelectOption +from nextcord.interactions import Interaction +from sqlalchemy import exc, select, update +from sqlalchemy.orm import Session from utils import VoteType, build_highcouncil_votes_str, get_election_name class VoteSelect(nextcord.ui.StringSelect): + """Vote view string select.""" + def __init__(self, options: list["SelectOption"], config: DakinyConfig, election_type: str) -> None: super().__init__( placeholder="Choose 1", @@ -30,7 +28,16 @@ class VoteSelect(nextcord.ui.StringSelect): self.config = config self.election_type = election_type - async def callback(self, interaction: Interaction): + async def callback(self, interaction: Interaction) -> None: + """ + Callback override for the StringSelect. + + Submits the High Council vote to database if a user is selected. Handles adding vs editing a vote. + Returns message informing user that vote has been recorded. + + Args: + interaction (Interaction): Interaction with this callback. + """ election_name: str = get_election_name(self.config) with Session(engine) as session: stmt = select(ElectionInformation.HighCouncilVoteMsgId).where( @@ -38,10 +45,7 @@ class VoteSelect(nextcord.ui.StringSelect): ) vote_msg_id = session.scalar(stmt) - if self.values[0] == "Abstain": - voted_for = None - else: - voted_for = int(self.values[0]) + voted_for = None if self.values[0] == "Abstain" else int(self.values[0]) new_vote = Votes( ElectionCycle=election_name, @@ -71,18 +75,13 @@ class VoteSelect(nextcord.ui.StringSelect): vote_msg: nextcord.Message = await interaction.channel.fetch_message(vote_msg_id) await vote_msg.edit(content=vote_message_str) - if voted_for is not None: - msg = f"You have voted for <@{voted_for}>" - else: - msg = "You have abstained from voting." + msg = f"You have voted for <@{voted_for}>" if voted_for is not None else "You have abstained from voting." await interaction.edit(content=msg, view=None) class VoteHighcouncilView(nextcord.ui.View): - """ - The button to press to start voting for HighCouncil. - """ + """The button to press to start voting for HighCouncil.""" def __init__(self, config: DakinyConfig, guild: nextcord.Guild): super().__init__(timeout=None) @@ -94,8 +93,10 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-cl", ) - async def vote_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def vote_cl(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to vote for Clan Leader. + Enforces only council elect vote. Gets election cycle, creates voting options for clan leader (checking if user has voted already), then sends the voting view. @@ -164,8 +165,10 @@ class VoteHighcouncilView(nextcord.ui.View): style=nextcord.ButtonStyle.primary, custom_id="vote-dm", ) - async def vote_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: + async def vote_dm(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002 """ + Button to vote for Discord Manager. + Enforces only council elect vote. Gets election cycle, creates voting options for discord manager (checking if user has voted already), then sends the voting view. diff --git a/config.py b/config.py index 84b2144..c7746da 100644 --- a/config.py +++ b/config.py @@ -1,15 +1,11 @@ -""" -File contains classes defining the Hydra configuraton structure. -""" +"""File contains classes defining the Hydra configuraton structure.""" from dataclasses import dataclass @dataclass class Params: - """ - Class defines the various configuration parameters. - """ + """Class defines the various configuration parameters.""" admin_channel_id: int # might want to rename this... announcement_category_id: int @@ -32,9 +28,7 @@ class Params: @dataclass class ElectionCycle: - """ - Class defines relevant election dates. - """ + """Class defines relevant election dates.""" election_start: str highcouncil_election_announce: str @@ -49,9 +43,7 @@ class ElectionCycle: @dataclass class DakinyConfig: - """ - Class defines all configs required for Dakiny Bot. - """ + """Class defines all configs required for Dakiny Bot.""" election_cycles: list[ElectionCycle] params: Params