Skip to content

Commit 470be16

Browse files
committed
moved all discord-specific utilities into their own file
1 parent 63fb91c commit 470be16

8 files changed

Lines changed: 119 additions & 116 deletions

File tree

src/discord-cluster-manager/cogs/admin_cog.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
from consts import GitHubGPU, ModalGPU
1414
from discord import app_commands
1515
from discord.ext import commands, tasks
16+
from discord_utils import leaderboard_name_autocomplete, send_discord_message, with_error_handling
1617
from leaderboard_db import LeaderboardItem, SubmissionItem
1718
from task import LeaderboardTask, make_task
1819
from ui.misc import ConfirmationView, DeleteConfirmationModal, GPUSelectionView
1920
from utils import (
2021
KernelBotError,
21-
leaderboard_name_autocomplete,
22-
send_discord_message,
2322
setup_logging,
24-
with_error_handling,
2523
)
2624

2725
if TYPE_CHECKING:

src/discord-cluster-manager/cogs/leaderboard_cog.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
)
1111
from discord import app_commands
1212
from discord.ext import commands
13+
from discord_utils import (
14+
get_user_from_id,
15+
leaderboard_name_autocomplete,
16+
send_discord_message,
17+
with_error_handling,
18+
)
1319
from leaderboard_db import (
1420
LeaderboardItem,
1521
LeaderboardRankedEntry,
@@ -22,11 +28,7 @@
2228
from ui.table import create_table
2329
from utils import (
2430
format_time,
25-
get_user_from_id,
26-
leaderboard_name_autocomplete,
27-
send_discord_message,
2831
setup_logging,
29-
with_error_handling,
3032
)
3133

3234
if TYPE_CHECKING:

src/discord-cluster-manager/cogs/misc_cog.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import psycopg2
66
from discord import app_commands
77
from discord.ext import commands
8+
from discord_utils import send_discord_message
89
from env import DATABASE_URL, DISABLE_SSL
9-
from utils import send_discord_message, setup_logging
10+
from utils import setup_logging
1011

1112
if TYPE_CHECKING:
1213
from ..bot import ClusterBot

src/discord-cluster-manager/cogs/submit_cog.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from consts import GPU, GPU_TO_SM, RankCriterion, SubmissionMode, get_gpu_by_name
1212
from discord import app_commands
1313
from discord.ext import commands
14+
from discord_utils import send_discord_message, with_error_handling
1415
from report import (
1516
MultiProgressReporter,
1617
RunProgressReporter,
@@ -21,9 +22,7 @@
2122
from task import LeaderboardTask, build_task_config
2223
from utils import (
2324
KernelBotError,
24-
send_discord_message,
2525
setup_logging,
26-
with_error_handling,
2726
)
2827

2928
logger = setup_logging()

src/discord-cluster-manager/cogs/verify_run_cog.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
from discord import app_commands
1515
from discord.app_commands import Choice
1616
from discord.ext import commands
17+
from discord_utils import send_discord_message, with_error_handling
1718
from leaderboard_db import RunItem, SubmissionItem
1819
from report import MultiProgressReporter
1920
from task import make_task
20-
from utils import send_discord_message, setup_logging, with_error_handling
21+
from utils import setup_logging
2122

2223
logger = setup_logging()
2324

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import functools
2+
import logging
3+
4+
import discord
5+
from utils import KernelBotError, setup_logging
6+
7+
logger = setup_logging(__name__)
8+
9+
10+
def with_error_handling(f: callable):
11+
@functools.wraps(f)
12+
async def wrap(self, interaction: discord.Interaction, *args, **kwargs):
13+
try:
14+
await f(self, interaction, *args, **kwargs)
15+
except KernelBotError as e:
16+
await send_discord_message(
17+
interaction,
18+
str(e),
19+
ephemeral=True,
20+
)
21+
except Exception as e:
22+
logging.exception("Unhandled exception %s", e, exc_info=e)
23+
await send_discord_message(
24+
interaction,
25+
"An unexpected error occurred. Please report this to the developers.",
26+
ephemeral=True,
27+
)
28+
29+
return wrap
30+
31+
32+
async def get_user_from_id(bot, id) -> str:
33+
with bot.leaderboard_db as db:
34+
return db.get_user_from_id(id) or id
35+
36+
37+
async def send_discord_message(
38+
interaction: discord.Interaction, msg: str, *, ephemeral=False, **kwargs
39+
) -> None:
40+
"""
41+
To get around response messages in slash commands that are
42+
called externally, send a message using the followup.
43+
"""
44+
if interaction.response.is_done():
45+
await interaction.followup.send(msg, ephemeral=ephemeral, **kwargs)
46+
else:
47+
await interaction.response.send_message(msg, ephemeral=ephemeral, **kwargs)
48+
49+
50+
async def send_logs(thread: discord.Thread, logs: str) -> None:
51+
"""Send logs to a Discord thread, splitting by lines and respecting Discord's character limit.
52+
53+
Args:
54+
thread: The Discord thread to send logs to
55+
logs: The log string to send
56+
"""
57+
# Split logs into lines
58+
log_lines = logs.splitlines()
59+
60+
current_chunk = []
61+
current_length = 0
62+
63+
for line in log_lines:
64+
# Add 1 for the newline character
65+
line_length = len(line) + 1
66+
67+
# If adding this line would exceed Discord's limit, send current chunk
68+
if current_length + line_length > 1990: # Leave room for code block markers
69+
if current_chunk:
70+
chunk_text = "\n".join(current_chunk)
71+
await thread.send(f"```\n{chunk_text}\n```")
72+
current_chunk = []
73+
current_length = 0
74+
75+
current_chunk.append(line)
76+
current_length += line_length
77+
78+
# Send any remaining lines
79+
if current_chunk:
80+
chunk_text = "\n".join(current_chunk)
81+
await thread.send(f"```\n{chunk_text}\n```")
82+
83+
84+
async def leaderboard_name_autocomplete(
85+
interaction: discord.Interaction,
86+
current: str,
87+
) -> list[discord.app_commands.Choice[str]]:
88+
"""Return leaderboard names that match the current typed name"""
89+
try:
90+
bot = interaction.client
91+
name_cache = bot.leaderboard_db.name_cache
92+
cached_value = name_cache[current]
93+
if cached_value is not None:
94+
return cached_value
95+
96+
with bot.leaderboard_db as db:
97+
leaderboards = db.get_leaderboard_names()
98+
filtered = [lb for lb in leaderboards if current.lower() in lb.lower()]
99+
name_cache[current] = [
100+
discord.app_commands.Choice(name=name, value=name) for name in filtered[:25]
101+
]
102+
return name_cache[current]
103+
except Exception as e:
104+
logger.exception("Error in leaderboard autocomplete", exc_info=e)
105+
return []

src/discord-cluster-manager/ui/misc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import discord
44
from discord import Interaction, SelectOption, ui
5-
from utils import KernelBotError, send_discord_message
5+
from discord_utils import send_discord_message
6+
from utils import KernelBotError
67

78

89
class GPUSelectionView(ui.View):

src/discord-cluster-manager/utils.py

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
import functools
21
import logging
32
import subprocess
43
from typing import Any, Optional
54

6-
import discord
7-
85

96
def setup_logging(name: Optional[str] = None):
107
"""Configure and setup logging for the application"""
@@ -26,31 +23,6 @@ def setup_logging(name: Optional[str] = None):
2623
return logger
2724

2825

29-
logger = setup_logging(__name__)
30-
31-
32-
def with_error_handling(f: callable):
33-
@functools.wraps(f)
34-
async def wrap(self, interaction: discord.Interaction, *args, **kwargs):
35-
try:
36-
await f(self, interaction, *args, **kwargs)
37-
except KernelBotError as e:
38-
await send_discord_message(
39-
interaction,
40-
str(e),
41-
ephemeral=True,
42-
)
43-
except Exception as e:
44-
logging.exception("Unhandled exception %s", e, exc_info=e)
45-
await send_discord_message(
46-
interaction,
47-
"An unexpected error occurred. Please report this to the developers.",
48-
ephemeral=True,
49-
)
50-
51-
return wrap
52-
53-
5426
class KernelBotError(Exception):
5527
"""
5628
This class represents an Exception that has been sanitized,
@@ -75,58 +47,6 @@ def get_github_branch_name():
7547
return "main"
7648

7749

78-
async def get_user_from_id(bot, id) -> str:
79-
with bot.leaderboard_db as db:
80-
return db.get_user_from_id(id) or id
81-
82-
83-
async def send_discord_message(
84-
interaction: discord.Interaction, msg: str, *, ephemeral=False, **kwargs
85-
) -> None:
86-
"""
87-
To get around response messages in slash commands that are
88-
called externally, send a message using the followup.
89-
"""
90-
if interaction.response.is_done():
91-
await interaction.followup.send(msg, ephemeral=ephemeral, **kwargs)
92-
else:
93-
await interaction.response.send_message(msg, ephemeral=ephemeral, **kwargs)
94-
95-
96-
async def send_logs(thread: discord.Thread, logs: str) -> None:
97-
"""Send logs to a Discord thread, splitting by lines and respecting Discord's character limit.
98-
99-
Args:
100-
thread: The Discord thread to send logs to
101-
logs: The log string to send
102-
"""
103-
# Split logs into lines
104-
log_lines = logs.splitlines()
105-
106-
current_chunk = []
107-
current_length = 0
108-
109-
for line in log_lines:
110-
# Add 1 for the newline character
111-
line_length = len(line) + 1
112-
113-
# If adding this line would exceed Discord's limit, send current chunk
114-
if current_length + line_length > 1990: # Leave room for code block markers
115-
if current_chunk:
116-
chunk_text = "\n".join(current_chunk)
117-
await thread.send(f"```\n{chunk_text}\n```")
118-
current_chunk = []
119-
current_length = 0
120-
121-
current_chunk.append(line)
122-
current_length += line_length
123-
124-
# Send any remaining lines
125-
if current_chunk:
126-
chunk_text = "\n".join(current_chunk)
127-
await thread.send(f"```\n{chunk_text}\n```")
128-
129-
13050
class LRUCache:
13151
def __init__(self, max_size: int):
13252
"""LRU Cache implementation, as functools.lru doesn't work in async code
@@ -215,27 +135,3 @@ def format_time(value: float | str, err: Optional[float | str] = None, scale=Non
215135
return f"{value:.0f} ± {err:.1f} {unit}"
216136
else:
217137
return f"{value:.0f} {unit}"
218-
219-
220-
async def leaderboard_name_autocomplete(
221-
interaction: discord.Interaction,
222-
current: str,
223-
) -> list[discord.app_commands.Choice[str]]:
224-
"""Return leaderboard names that match the current typed name"""
225-
try:
226-
bot = interaction.client
227-
name_cache = bot.leaderboard_db.name_cache
228-
cached_value = name_cache[current]
229-
if cached_value is not None:
230-
return cached_value
231-
232-
with bot.leaderboard_db as db:
233-
leaderboards = db.get_leaderboard_names()
234-
filtered = [lb for lb in leaderboards if current.lower() in lb.lower()]
235-
name_cache[current] = [
236-
discord.app_commands.Choice(name=name, value=name) for name in filtered[:25]
237-
]
238-
return name_cache[current]
239-
except Exception as e:
240-
logger.exception("Error in leaderboard autocomplete", exc_info=e)
241-
return []

0 commit comments

Comments
 (0)