Skip to content

Commit ba117c8

Browse files
3.14 in snekbox (#3324)
* Replace 3.12 support in snekbox for 3.14-dev * Add extra info about pre-release versions in snekbox output * Dynamically get the default snekbox Python version by looking at the first supported version * Move snekbox help docs to the @command decorator This allows us to use a f-string to get the supported Python versions, instead of hard coding * Update snekbox tests to use new default python version
2 parents c562644 + e3339bb commit ba117c8

File tree

4 files changed

+57
-41
lines changed

4 files changed

+57
-41
lines changed

bot/exts/utils/snekbox/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from bot.bot import Bot
2-
from bot.exts.utils.snekbox._cog import CodeblockConverter, Snekbox
2+
from bot.exts.utils.snekbox._cog import CodeblockConverter, Snekbox, SupportedPythonVersions
33
from bot.exts.utils.snekbox._eval import EvalJob, EvalResult
44

5-
__all__ = ("CodeblockConverter", "EvalJob", "EvalResult", "Snekbox")
5+
__all__ = ("CodeblockConverter", "EvalJob", "EvalResult", "Snekbox", "SupportedPythonVersions")
66

77

88
async def setup(bot: Bot) -> None:

bot/exts/utils/snekbox/_cog.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def print_last_line():
8787
REDO_EMOJI = "\U0001f501" # :repeat:
8888
REDO_TIMEOUT = 30
8989

90-
SupportedPythonVersions = Literal["3.12", "3.13", "3.13t"]
90+
SupportedPythonVersions = Literal["3.13", "3.13t", "3.14"]
9191

9292
class FilteredFiles(NamedTuple):
9393
allowed: list[FileAttachment]
@@ -569,7 +569,29 @@ async def run_job(
569569
break
570570
log.info(f"Re-evaluating code from message {ctx.message.id}:\n{job}")
571571

572-
@command(name="eval", aliases=("e",), usage="[python_version] <code, ...>")
572+
@command(
573+
name="eval",
574+
aliases=("e",),
575+
usage="[python_version] <code, ...>",
576+
help=f"""
577+
Run Python code and get the results.
578+
579+
This command supports multiple lines of code, including formatted code blocks.
580+
Code can be re-evaluated by editing the original message within 10 seconds and
581+
clicking the reaction that subsequently appears.
582+
583+
The starting working directory `/home`, is a writeable temporary file system.
584+
Files created, excluding names with leading underscores, will be uploaded in the response.
585+
586+
If multiple codeblocks are in a message, all of them will be joined and evaluated,
587+
ignoring the text outside them.
588+
589+
The currently supported versions are {", ".join(get_args(SupportedPythonVersions))}.
590+
591+
We've done our best to make this sandboxed, but do let us know if you manage to find an
592+
issue with it!
593+
"""
594+
)
573595
@guild_only()
574596
@redirect_output(
575597
destination_channel=Channels.bot_commands,
@@ -585,26 +607,9 @@ async def eval_command(
585607
*,
586608
code: CodeblockConverter
587609
) -> None:
588-
"""
589-
Run Python code and get the results.
590-
591-
This command supports multiple lines of code, including formatted code blocks.
592-
Code can be re-evaluated by editing the original message within 10 seconds and
593-
clicking the reaction that subsequently appears.
594-
595-
The starting working directory `/home`, is a writeable temporary file system.
596-
Files created, excluding names with leading underscores, will be uploaded in the response.
597-
598-
If multiple codeblocks are in a message, all of them will be joined and evaluated,
599-
ignoring the text outside them.
600-
601-
The currently supported verisons are 3.12, 3.13, and 3.13t.
602-
603-
We've done our best to make this sandboxed, but do let us know if you manage to find an
604-
issue with it!
605-
"""
610+
"""Run Python code and get the results."""
606611
code: list[str]
607-
python_version = python_version or "3.12"
612+
python_version = python_version or get_args(SupportedPythonVersions)[0]
608613
job = EvalJob.from_code("\n".join(code)).as_version(python_version)
609614
await self.run_job(ctx, job)
610615

bot/exts/utils/snekbox/_eval.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class EvalJob:
2626
args: list[str]
2727
files: list[FileAttachment] = field(default_factory=list)
2828
name: str = "eval"
29-
version: SupportedPythonVersions = "3.12"
29+
version: SupportedPythonVersions = "3.13"
3030

3131
@classmethod
3232
def from_code(cls, code: str, path: str = "main.py") -> EvalJob:
@@ -144,7 +144,12 @@ def get_failed_files_str(self, char_max: int = 85) -> str:
144144

145145
def get_status_message(self, job: EvalJob) -> str:
146146
"""Return a user-friendly message corresponding to the process's return code."""
147-
version_text = job.version.replace("t", " [free threaded](<https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython>)")
147+
if job.version == "3.13t":
148+
version_text = job.version.replace("t", " [free threaded](<https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython>)")
149+
elif job.version == "3.14":
150+
version_text = "3.14 [pre-release](<https://docs.python.org/3.14/whatsnew/3.14.html#development>)"
151+
else:
152+
version_text = job.version
148153
msg = f"Your {version_text} {job.name} job"
149154

150155
if self.returncode is None:

tests/bot/exts/utils/snekbox/test_snekbox.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import unittest
33
from base64 import b64encode
4+
from typing import get_args
45
from unittest.mock import AsyncMock, MagicMock, Mock, call, create_autospec, patch
56

67
from discord import AllowedMentions
@@ -10,7 +11,7 @@
1011
from bot import constants
1112
from bot.errors import LockedResourceError
1213
from bot.exts.utils import snekbox
13-
from bot.exts.utils.snekbox import EvalJob, EvalResult, Snekbox
14+
from bot.exts.utils.snekbox import EvalJob, EvalResult, Snekbox, SupportedPythonVersions
1415
from bot.exts.utils.snekbox._io import FileAttachment
1516
from tests.helpers import MockBot, MockContext, MockMember, MockMessage, MockReaction, MockUser
1617

@@ -21,6 +22,7 @@ def setUp(self):
2122
self.bot = MockBot()
2223
self.cog = Snekbox(bot=self.bot)
2324
self.job = EvalJob.from_code("import random")
25+
self.default_version = get_args(SupportedPythonVersions)[0]
2426

2527
@staticmethod
2628
def code_args(code: str) -> tuple[EvalJob]:
@@ -35,7 +37,7 @@ async def test_post_job(self):
3537
context_manager = MagicMock()
3638
context_manager.__aenter__.return_value = resp
3739
self.bot.http_session.post.return_value = context_manager
38-
py_version = "3.12"
40+
py_version = self.default_version
3941
job = EvalJob.from_code("import random").as_version(py_version)
4042
self.assertEqual(await self.cog.post_job(job), EvalResult("Hi", 137))
4143

@@ -104,9 +106,13 @@ def test_prepare_timeit_input(self):
104106
def test_eval_result_message(self):
105107
"""EvalResult.get_message(), should return message."""
106108
cases = (
107-
("ERROR", None, ("Your 3.12 eval job has failed", "ERROR", "")),
108-
("", 128 + snekbox._eval.SIGKILL, ("Your 3.12 eval job timed out or ran out of memory", "", "")),
109-
("", 255, ("Your 3.12 eval job has failed", "A fatal NsJail error occurred", ""))
109+
("ERROR", None, (f"Your {self.default_version} eval job has failed", "ERROR", "")),
110+
(
111+
"",
112+
128 + snekbox._eval.SIGKILL,
113+
(f"Your {self.default_version} eval job timed out or ran out of memory", "", "")
114+
),
115+
("", 255, (f"Your {self.default_version} eval job has failed", "A fatal NsJail error occurred", ""))
110116
)
111117
for stdout, returncode, expected in cases:
112118
exp_msg, exp_err, exp_files_err = expected
@@ -178,8 +184,8 @@ def test_eval_result_message_valid_signal(self, mock_signals: Mock):
178184
mock_signals.return_value.name = "SIGTEST"
179185
result = EvalResult(stdout="", returncode=127)
180186
self.assertEqual(
181-
result.get_status_message(EvalJob([], version="3.12")),
182-
"Your 3.12 eval job has completed with return code 127 (SIGTEST)"
187+
result.get_status_message(EvalJob([])),
188+
f"Your {self.default_version} eval job has completed with return code 127 (SIGTEST)"
183189
)
184190

185191
def test_eval_result_status_emoji(self):
@@ -253,7 +259,7 @@ async def test_eval_command_evaluate_once(self):
253259
self.cog.send_job = AsyncMock(return_value=response)
254260
self.cog.continue_job = AsyncMock(return_value=None)
255261

256-
await self.cog.eval_command(self.cog, ctx=ctx, python_version="3.12", code=["MyAwesomeCode"])
262+
await self.cog.eval_command(self.cog, ctx=ctx, python_version=self.default_version, code=["MyAwesomeCode"])
257263
job = EvalJob.from_code("MyAwesomeCode")
258264
self.cog.send_job.assert_called_once_with(ctx, job)
259265
self.cog.continue_job.assert_called_once_with(ctx, response, "eval")
@@ -267,7 +273,7 @@ async def test_eval_command_evaluate_twice(self):
267273
self.cog.continue_job = AsyncMock()
268274
self.cog.continue_job.side_effect = (EvalJob.from_code("MyAwesomeFormattedCode"), None)
269275

270-
await self.cog.eval_command(self.cog, ctx=ctx, python_version="3.12", code=["MyAwesomeCode"])
276+
await self.cog.eval_command(self.cog, ctx=ctx, python_version=self.default_version, code=["MyAwesomeCode"])
271277

272278
expected_job = EvalJob.from_code("MyAwesomeFormattedCode")
273279
self.cog.send_job.assert_called_with(ctx, expected_job)
@@ -311,7 +317,7 @@ async def test_send_job(self):
311317
ctx.send.assert_called_once()
312318
self.assertEqual(
313319
ctx.send.call_args.args[0],
314-
":warning: Your 3.12 eval job has completed "
320+
f":warning: Your {self.default_version} eval job has completed "
315321
"with return code 0.\n\n```ansi\n[No output]\n```"
316322
)
317323
allowed_mentions = ctx.send.call_args.kwargs["allowed_mentions"]
@@ -335,13 +341,13 @@ async def test_send_job_with_paste_link(self):
335341
mocked_filter_cog.filter_snekbox_output = AsyncMock(return_value=(False, []))
336342
self.bot.get_cog.return_value = mocked_filter_cog
337343

338-
job = EvalJob.from_code("MyAwesomeCode").as_version("3.12")
344+
job = EvalJob.from_code("MyAwesomeCode").as_version(self.default_version)
339345
await self.cog.send_job(ctx, job),
340346

341347
ctx.send.assert_called_once()
342348
self.assertEqual(
343349
ctx.send.call_args.args[0],
344-
":white_check_mark: Your 3.12 eval job "
350+
f":white_check_mark: Your {self.default_version} eval job "
345351
"has completed with return code 0."
346352
"\n\n```ansi\nWay too long beard\n```\nFull output: lookatmybeard.com"
347353
)
@@ -362,13 +368,13 @@ async def test_send_job_with_non_zero_eval(self):
362368
mocked_filter_cog.filter_snekbox_output = AsyncMock(return_value=(False, []))
363369
self.bot.get_cog.return_value = mocked_filter_cog
364370

365-
job = EvalJob.from_code("MyAwesomeCode").as_version("3.12")
371+
job = EvalJob.from_code("MyAwesomeCode").as_version(self.default_version)
366372
await self.cog.send_job(ctx, job),
367373

368374
ctx.send.assert_called_once()
369375
self.assertEqual(
370376
ctx.send.call_args.args[0],
371-
":x: Your 3.12 eval job has completed with return code 127."
377+
f":x: Your {self.default_version} eval job has completed with return code 127."
372378
"\n\n```ansi\nERROR\n```"
373379
)
374380

@@ -395,13 +401,13 @@ async def test_send_job_with_disallowed_file_ext(self):
395401
mocked_filter_cog.filter_snekbox_output = AsyncMock(return_value=(False, disallowed_exts))
396402
self.bot.get_cog.return_value = mocked_filter_cog
397403

398-
job = EvalJob.from_code("MyAwesomeCode").as_version("3.12")
404+
job = EvalJob.from_code("MyAwesomeCode").as_version(self.default_version)
399405
await self.cog.send_job(ctx, job),
400406

401407
ctx.send.assert_called_once()
402408
res = ctx.send.call_args.args[0]
403409
self.assertTrue(
404-
res.startswith(":white_check_mark: Your 3.12 eval job has completed with return code 0.")
410+
res.startswith(f":white_check_mark: Your {self.default_version} eval job has completed with return code 0.")
405411
)
406412
self.assertIn("Files with disallowed extensions can't be uploaded: **.disallowed, .disallowed2, ...**", res)
407413

0 commit comments

Comments
 (0)