make docstrings consistent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
leanderandClaude Opus 5.5 committed 2026-11-03 02:07:01 +01:00
1 parent a4b889cf54
commit 4d58d33063
12 files changed
+181 -81

No files matched your search

+6 -6
View File
@@ -31,7 +31,7 @@ class BotInfo(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# Memory usage
@@ -99,7 +99,7 @@ class BotInfo(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
embed = discord.Embed(color=interaction.client.user.color)
@@ -115,7 +115,7 @@ class BotInfo(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
embed = discord.Embed(color=interaction.client.user.color)
@@ -128,7 +128,7 @@ class BotInfo(commands.Cog):
Returns
----------
(int) The bot's ping in milliseconds
(int): The bot's ping in milliseconds
"""
return int(self.bot.latency * 1000)
@@ -139,7 +139,7 @@ class BotInfo(commands.Cog):
Returns
----------
(str) The bot's uptime in human readable format
(str): The bot's uptime in human readable format
"""
now = time()
@@ -157,7 +157,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(BotInfo(bot))
+9 -9
View File
@@ -29,7 +29,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
activity_type (str): The type of activity to set the bot's status to
message (str): The message to set the bot's status to
status_type (str): The type of status to set the bot's status to
@@ -60,7 +60,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
guild_id (int): The ID of the guild to leave
"""
@@ -104,7 +104,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
"""
if not ctx.invoked_subcommand:
@@ -117,7 +117,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
cog (str): The name of the cog to disable
"""
@@ -141,7 +141,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
cog (str): The name of the cog to enable
"""
@@ -165,7 +165,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
cog (str): The name of the cog to reload
"""
@@ -189,7 +189,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
"""
extensions = [f"cogs.{file[:-3]}" for file in listdir("./src/cogs") if file.endswith(".py")]
@@ -205,7 +205,7 @@ class DevTools(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
"""
# Only loaded cogs can be reloaded. Copied to a list since reloading modifies bot.extensions
@@ -267,7 +267,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(DevTools(bot))
+7 -7
View File
@@ -27,7 +27,7 @@ class Errors(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
ctx (commands.Context): The command context
"""
self.bot.logger.info(
@@ -45,7 +45,7 @@ class Errors(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
command (app_commands.Command | app_commands.ContextMenu): Command object
"""
@@ -62,8 +62,8 @@ class Errors(commands.Cog):
Parameters
----------
ctx (commands.Context): Context object
error (commands.CommandError): Eror context object
ctx (commands.Context): The command context
error (commands.CommandError): The error the command raised
"""
# Failed commands don't count towards the cooldown, unless the failure is the cooldown itself.
@@ -133,8 +133,8 @@ class Errors(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
error (app_commands.AppCommandError): Eror context object
interaction (discord.Interaction): The slash command interaction
error (app_commands.AppCommandError): The error the command raised
"""
# The command may have deferred or responded before it failed
@@ -196,7 +196,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(Errors(bot))
+33 -5
View File
@@ -42,6 +42,10 @@ class Galtinn(commands.Cog):
self.session: aiohttp.ClientSession | None = None
async def cog_load(self):
"""
Open the Galtinn session, set up the database listener and start the background tasks
"""
# One session for all Galtinn requests, so connections are reused
self.session = aiohttp.ClientSession(
headers={"Authorization": f"Token {self.bot.galtinn['auth_token']}"},
@@ -55,6 +59,10 @@ class Galtinn(commands.Cog):
self.verification_cleanup.start()
async def cog_unload(self):
"""
Stop the background tasks and close the database listener and the Galtinn session
"""
self.bot.logger.info("Unloading cog")
self.membership_check.cancel()
self.verification_cleanup.cancel()
@@ -122,6 +130,10 @@ class Galtinn(commands.Cog):
def on_listen_conn_terminated(self, conn: asyncpg.Connection):
"""
Starts reconnecting the listener if its connection is lost, e.g. on a database restart
Parameters
----------
conn (asyncpg.Connection): The connection that was lost
"""
# Connections closed on cog unload are detached before closing
@@ -153,6 +165,13 @@ class Galtinn(commands.Cog):
async def process_notification(self, conn: asyncpg.Connection, pid: int, channel: str, payload: str):
"""
Applies Galtinn roles to a user that has just completed verification
Parameters
----------
conn (asyncpg.Connection): The connection that received the notification
pid (int): The ID of the database process that sent the notification
channel (str): The notification channel
payload (str): The Discord user ID and the Galtinn user ID, separated by a space
"""
self.bot.logger.info("Received galtinn_auth_complete event from database")
@@ -242,6 +261,10 @@ class Galtinn(commands.Cog):
async def fetch_galtinn_users(self, discord_id: int | None = None) -> Users | None:
"""
Attempts to fetch user(s) from Galtinn. If no Discord ID is given, all users are fetched
Parameters
----------
discord_id (int | None): The Discord ID of the user to fetch
"""
params = {"no_discord_id": False}
@@ -255,6 +278,11 @@ class Galtinn(commands.Cog):
) -> DiscordProfiles | None:
"""
Attempts to fetch discord profile(s) from Galtinn. If no parameters are given, all profiles are fetched
Parameters
----------
galtinn_user_id (int | None): Only fetch the profile of this Galtinn user
discord_id (int | None): Only fetch the profile with this Discord ID
"""
params = {}
@@ -278,7 +306,7 @@ class Galtinn(commands.Cog):
Returns
----------
set(int): Set of role ids
(set[int]): Set of role ids
"""
all_roles = {self.bot.galtinn_roles["volunteer"], self.bot.galtinn_roles["member"]}
@@ -493,7 +521,7 @@ class Galtinn(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
await interaction.response.defer(ephemeral=True)
@@ -566,7 +594,7 @@ class Galtinn(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# TODO: add confirmation button
@@ -627,7 +655,7 @@ class Galtinn(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# Discord ignores guild_only on subcommands, and the rest of the group has to work in DMs
@@ -686,7 +714,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(Galtinn(bot))
+24 -24
View File
@@ -31,7 +31,7 @@ class Info(commands.Cog):
roles (list[discord.Role]): List of roles
Returns
-------
----------
(str): String of roles
"""
@@ -50,11 +50,11 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
join_method (callable): Function to join the boosters with
Returns
-------
----------
(str): String of boosters
"""
@@ -78,7 +78,7 @@ class Info(commands.Cog):
members (list[discord.Member]): List of members
Returns
-------
----------
(str): String of members
"""
@@ -98,7 +98,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# Days since guild was created
@@ -254,7 +254,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
roles = self.construct_role_string(interaction.guild.roles)
@@ -277,7 +277,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
if len(interaction.guild.premium_subscribers) == 0:
@@ -302,7 +302,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
if not interaction.guild.icon:
@@ -323,7 +323,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
if not interaction.guild.splash:
@@ -344,7 +344,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
if not interaction.guild.banner:
@@ -367,7 +367,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
role (discord.Role): Role to fetch information about
"""
@@ -413,7 +413,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
channel (discord.TextChannel): Text channel to fetch information about
"""
@@ -447,7 +447,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
channel (discord.VoiceChannel): Voice channel to fetch information about
"""
@@ -472,7 +472,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
roles = sorted(
@@ -501,7 +501,7 @@ class Info(commands.Cog):
Parameters
----------
ctx (commands.Context): Command context object
ctx (commands.Context): The command context
emoji (discord.Emoji): Emoji to fetch information about
"""
@@ -534,7 +534,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# Sort members by creation date
@@ -566,7 +566,7 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
# Sort members by creation date
@@ -597,8 +597,8 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
user (discord.Member, optional): User to get information about. Defaults to None (invoker).
interaction (discord.Interaction): The slash command interaction
user (discord.Member | None): User to get information about. Defaults to the invoking user
"""
if not user:
@@ -693,8 +693,8 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
user (discord.Member, optional): User to get roles of. Defaults to None (invoker).
interaction (discord.Interaction): The slash command interaction
user (discord.Member | None): User to get roles of. Defaults to the invoking user
"""
if not user:
@@ -724,8 +724,8 @@ class Info(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
user (discord.User, optional): User to get avatar of. Defaults to None (invoker).
interaction (discord.Interaction): The slash command interaction
user (discord.User | None): User to get avatar of. Defaults to the invoking user
"""
if not user:
@@ -743,7 +743,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(Info(bot))
+14 -13
View File
@@ -12,7 +12,7 @@ async def send_as_txt_file(interaction: discord.Interaction, content: str, filen
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
content (str): String that's too long to send
filename (str): Name of the file shown in Discord
"""
@@ -27,7 +27,7 @@ async def get_discord_guild(bot: commands.Bot, id: int) -> discord.Guild | None:
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
id (int): ID of the object
Returns
@@ -56,7 +56,7 @@ async def get_guild_member(bot: commands.Bot, guild: discord.Guild, user_id: int
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
guild (discord.Guild): The discord guild to fetch the member from
user_id (int): ID of the user
@@ -87,7 +87,7 @@ async def get_guild_roles(bot: commands.Bot, guild: discord.Guild, role_ids: set
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
guild (discord.Guild): The discord guild to fetch the roles from
role_ids (set[int]): IDs of the roles
@@ -123,11 +123,13 @@ class ScrollerButton(discord.ui.Button):
):
"""
Parameters
-----------
----------
paginator (Paginator): The paginator object that contains the data to be paginated
button_action (callable): The function that returns the requested page
content_constructor (callable): A function that takes a paginator object and a page number and returns an embed
owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the button
label (str): The text on the button
disabled (bool): Whether the button is greyed out
"""
super().__init__(label=label, disabled=disabled)
@@ -141,8 +143,8 @@ class ScrollerButton(discord.ui.Button):
What to do when the button is pressed
Parameters
-----------
interaction (discord.Interaction): Slash command context object
----------
interaction (discord.Interaction): The interaction that triggered this
"""
if interaction.user.id != self.owner.id:
@@ -163,20 +165,20 @@ class Scroller(discord.ui.View):
def __init__(
self,
paginatior: misc_utils.Paginator,
paginator: misc_utils.Paginator,
owner: discord.User | discord.Member,
content_constructor: callable = None,
):
"""
Parameters
-----------
----------
paginator (Paginator): The paginator object that contains the data to be paginated
owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the buttons
content_constructor (callable): A function that takes a paginator object and a page number and returns an embed
"""
super().__init__()
self.paginator = paginatior
self.paginator = paginator
self.content_constructor = content_constructor if content_constructor else self.__default_content_constructor
self.add_item(
@@ -225,7 +227,7 @@ class Scroller(discord.ui.View):
Constructs the embed to be displayed
Parameters
-----------
----------
base_embed (discord.Embed): The base embed to add fields to
"""
@@ -237,8 +239,7 @@ class Scroller(discord.ui.View):
Parameters
----------
paginator (Paginator): Paginator dataclass
page (list): List of streaks to display on a page
page (list): The items to show on the page
embed (discord.Embed): Embed to add fields to
"""
+7 -7
View File
@@ -6,11 +6,11 @@ def error_warning(text: str) -> discord.Embed:
Creates an embed with a specified error message based on a warning template
Parameters
-----------
----------
text (str): The error message
Returns
-----------
----------
(discord.Embed): An embed object based on the template with the specified text
"""
@@ -23,11 +23,11 @@ def error_fatal(text: str) -> discord.Embed:
Creates an embed with a specified error message based on a warning template
Parameters
-----------
----------
text (str): The error message
Returns
-----------
----------
(discord.Embed): An embed object based on the template with the specified text
"""
@@ -40,12 +40,12 @@ def success(text: str) -> discord.Embed:
Creates an embed with a specified message using a template signifying success.
Parameters
-----------
----------
text (str): The message
Returns
-----------
discord.Embed: An embed object based on the template with the specified text
----------
(discord.Embed): An embed object based on the template with the specified text
"""
embed = discord.Embed(color=discord.Color.green(), description=f"✅ {text}")
+10 -10
View File
@@ -26,7 +26,7 @@ async def query_api(session: aiohttp.ClientSession, params: dict) -> dict:
Returns
----------
dict: The JSON response
(dict): The JSON response
Raises
----------
@@ -53,7 +53,7 @@ async def fetch_article(session: aiohttp.ClientSession, title: str):
Returns
----------
tuple: The title, url, text and image of the article
(tuple): The title, url, text and image of the article
"""
# The wiki renders the article to HTML for us, so templates are already expanded
@@ -194,7 +194,7 @@ def viteboka_embed(title: str, url: str, text: str, image: str):
Returns
----------
discord.Embed: The embed
(discord.Embed): The embed
"""
embed = discord.Embed(color=discord.Color.orange(), title=title, url=url, description=text)
@@ -205,7 +205,7 @@ def viteboka_embed(title: str, url: str, text: str, image: str):
class VitebokaException(Exception):
pass
"""Raised when an article can't be fetched. The message is shown to the user"""
class Viteboka(commands.Cog):
@@ -233,7 +233,7 @@ class Viteboka(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
query (str): The search query
"""
@@ -292,7 +292,7 @@ class Viteboka(commands.Cog):
Parameters
----------
interaction (discord.Interaction): Slash command context object
interaction (discord.Interaction): The slash command interaction
"""
await interaction.response.defer()
@@ -336,7 +336,7 @@ class ArticleButton(discord.ui.Button):
):
"""
Parameters
-----------
----------
owner (discord.User | discord.Member): The user that invoked the paginator. Only this user can use the button
label (str): The label of the button
article_title (str): The title of the article the button is associated with
@@ -351,8 +351,8 @@ class ArticleButton(discord.ui.Button):
What to do when the button is pressed
Parameters
-----------
interaction (discord.Interaction): Slash command context object
----------
interaction (discord.Interaction): The interaction that triggered this
"""
await interaction.response.defer()
@@ -378,7 +378,7 @@ async def setup(bot: commands.Bot):
Parameters
----------
bot (commands.Bot): Bot instance
bot (commands.Bot): The bot instance
"""
await bot.add_cog(Viteboka(bot))
+4
View File
@@ -8,6 +8,10 @@ class BotLogger:
"""Logging config for the bot"""
def __init__(self):
"""
Log to the console and to a file that rotates at midnight
"""
logger = logging.getLogger("")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
+18
View File
@@ -5,12 +5,16 @@ from pydantic import Field
class GroupDiscordRole(BaseModel):
"""A Discord role connected to a Galtinn group"""
id: int
discord_id: int
description: str
class GroupProfile(BaseModel):
"""Extra information about a Galtinn group, including its Discord roles"""
id: int
posix_name: str
description: str
@@ -19,18 +23,24 @@ class GroupProfile(BaseModel):
class Group(BaseModel):
"""A Galtinn group"""
id: int
name: str
profile: Optional[GroupProfile]
class UserDiscordProfile(BaseModel):
"""The link between a Galtinn user and a Discord user"""
id: int
discord_id: int
user: int
class DuskenUser(BaseModel):
"""A Galtinn user"""
id: int
username: str
is_volunteer: bool
@@ -40,6 +50,8 @@ class DuskenUser(BaseModel):
class BasicResponse(BaseModel):
"""A page of results from a paginated Galtinn endpoint"""
count: int
next: Optional[str]
previous: Optional[str]
@@ -47,12 +59,18 @@ class BasicResponse(BaseModel):
class Users(BasicResponse):
"""A page of Galtinn users"""
results: list[DuskenUser]
class DiscordProfiles(BasicResponse):
"""A page of Discord profiles"""
results: list[UserDiscordProfile]
class Groups(BasicResponse):
"""A page of Galtinn groups"""
results: list[Group]
+18
View File
@@ -16,7 +16,13 @@ load_dotenv()
class Bot(commands.Bot):
"""The bot, configured from environment variables"""
def __init__(self):
"""
Read the configuration from the environment
"""
super().__init__(
command_prefix=commands.when_mentioned_or(os.environ["BOT_PREFIX"]),
case_insensitive=True,
@@ -48,6 +54,10 @@ class Bot(commands.Bot):
}
async def setup_hook(self):
"""
Open the HTTP session and database pool, load the cogs and sync the slash commands
"""
self.http_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=15))
# DB needs to be setup here because contructor is not async
@@ -97,6 +107,10 @@ class Bot(commands.Bot):
"""
Close the HTTP session and database pool after the bot has shut down. This isn't done in close(),
because bot.run doesn't wait for a close() started by the SIGTERM handler to finish
Parameters
----------
*args (tuple): The exception type, value and traceback if the bot stopped because of an error
"""
await super().__aexit__(*args)
@@ -112,6 +126,10 @@ bot = Bot()
@bot.event
async def on_ready():
"""
Print the bot's info and set its presence once it's connected
"""
if not hasattr(bot, "uptime"):
bot.uptime = time()
+31
View File
@@ -21,6 +21,14 @@ GALTINN_AUTH_TOKEN = os.environ["GALTINN_AUTH_TOKEN"]
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Open the database pool and HTTP session on startup, and close them on shutdown
Parameters
----------
app (FastAPI): The app
"""
credentials = {
"host": os.environ["DATABASE_HOST"],
"database": os.environ["DATABASE_NAME"],
@@ -45,11 +53,25 @@ templates = Jinja2Templates(directory="src/templates")
@app.get("/")
async def index():
"""
Tell visitors there's nothing to see here
"""
return "Hei du! Her skal ikke du drive og luske!"
@app.get("/callback")
async def callback(request: Request, state: str, code: str | None = None):
"""
Finish a Galtinn login: link the Galtinn user to the Discord user and tell the bot
Parameters
----------
request (Request): The request
state (str): Identifies the pending verification
code (str | None): The authorization code from Galtinn. Missing if the login was cancelled or failed
"""
# Claim the pending verification. Deleting it up front makes the state single use
verification = await app.state.pool.fetchrow(
"""
@@ -175,4 +197,13 @@ async def callback(request: Request, state: str, code: str | None = None):
@app.get("/success/{name}")
async def success(request: Request, name: str):
"""
Show the success page after a verification
Parameters
----------
request (Request): The request
name (str): The Galtinn username to show
"""
return templates.TemplateResponse(request, "success.html", {"name": name})