From 63ed6dd9e286d1e7d314e6108deeb5e86c08a247 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 14:20:04 +0100 Subject: [PATCH 1/7] Re-order SupportedPythonVersions to default to using 3.14.0 --- bot/exts/utils/snekbox/_cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/snekbox/_cog.py b/bot/exts/utils/snekbox/_cog.py index 39aa064aeb..f8651eda5d 100644 --- a/bot/exts/utils/snekbox/_cog.py +++ b/bot/exts/utils/snekbox/_cog.py @@ -87,7 +87,7 @@ def print_last_line(): REDO_EMOJI = "\U0001f501" # :repeat: REDO_TIMEOUT = 30 -SupportedPythonVersions = Literal["3.13", "3.13t", "3.14"] +SupportedPythonVersions = Literal["3.14", "3.13", "3.13t"] class FilteredFiles(NamedTuple): allowed: list[FileAttachment] From 13443682ca58aabb6632f47de70477313d4c97cb Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 14:20:27 +0100 Subject: [PATCH 2/7] Remove notice about 3.14 being pre-release --- bot/exts/utils/snekbox/_eval.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bot/exts/utils/snekbox/_eval.py b/bot/exts/utils/snekbox/_eval.py index 6136b6a811..3d2dd3201b 100644 --- a/bot/exts/utils/snekbox/_eval.py +++ b/bot/exts/utils/snekbox/_eval.py @@ -146,8 +146,6 @@ def get_status_message(self, job: EvalJob) -> str: """Return a user-friendly message corresponding to the process's return code.""" if job.version == "3.13t": version_text = job.version.replace("t", " [free threaded]()") - elif job.version == "3.14": - version_text = "3.14 [pre-release]()" else: version_text = job.version msg = f"Your {version_text} {job.name} job" From e81b0f13ef0cfeb11a53d52c6b43c976184235f7 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 14:45:23 +0100 Subject: [PATCH 3/7] Move snekbox operational and typing constants to a new file This will allow all other utility files within the snekbox cog to access types such as `SupportedPythonVersions` without causing circular import issues. --- bot/exts/utils/snekbox/__init__.py | 3 ++- bot/exts/utils/snekbox/_cog.py | 33 +++++++++++----------------- bot/exts/utils/snekbox/_constants.py | 22 +++++++++++++++++++ 3 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 bot/exts/utils/snekbox/_constants.py diff --git a/bot/exts/utils/snekbox/__init__.py b/bot/exts/utils/snekbox/__init__.py index fa91d0d6f3..b6ad82c7e3 100644 --- a/bot/exts/utils/snekbox/__init__.py +++ b/bot/exts/utils/snekbox/__init__.py @@ -1,5 +1,6 @@ from bot.bot import Bot -from bot.exts.utils.snekbox._cog import CodeblockConverter, Snekbox, SupportedPythonVersions +from bot.exts.utils.snekbox._cog import CodeblockConverter, Snekbox +from bot.exts.utils.snekbox._constants import SupportedPythonVersions from bot.exts.utils.snekbox._eval import EvalJob, EvalResult __all__ = ("CodeblockConverter", "EvalJob", "EvalResult", "Snekbox", "SupportedPythonVersions") diff --git a/bot/exts/utils/snekbox/_cog.py b/bot/exts/utils/snekbox/_cog.py index f8651eda5d..5fb160a5f2 100644 --- a/bot/exts/utils/snekbox/_cog.py +++ b/bot/exts/utils/snekbox/_cog.py @@ -1,12 +1,11 @@ from __future__ import annotations import contextlib -import re from collections.abc import Iterable from functools import partial from operator import attrgetter from textwrap import dedent -from typing import Literal, NamedTuple, TYPE_CHECKING, get_args +from typing import NamedTuple, TYPE_CHECKING, get_args from discord import AllowedMentions, HTTPException, Interaction, Message, NotFound, Reaction, User, enums, ui from discord.ext.commands import Cog, Command, Context, Converter, command, guild_only @@ -19,6 +18,18 @@ from bot.decorators import redirect_output from bot.exts.filtering._filter_lists.extension import TXT_LIKE_FILES from bot.exts.help_channels._channel import is_help_forum_post +from bot.exts.utils.snekbox._constants import ( + ANSI_REGEX, + ESCAPE_REGEX, + MAX_OUTPUT_BLOCK_CHARS, + MAX_OUTPUT_BLOCK_LINES, + NO_SNEKBOX_CATEGORIES, + NO_SNEKBOX_CHANNELS, + REDO_EMOJI, + REDO_TIMEOUT, + SNEKBOX_ROLES, + SupportedPythonVersions, +) from bot.exts.utils.snekbox._eval import EvalJob, EvalResult from bot.exts.utils.snekbox._io import FileAttachment from bot.log import get_logger @@ -29,9 +40,6 @@ log = get_logger(__name__) -ANSI_REGEX = re.compile(r"\N{ESC}\[[0-9;:]*m") -ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}") - # The timeit command should only output the very last line, so all other output should be suppressed. # This will be used as the setup code along with any setup code provided. TIMEIT_SETUP_WRAPPER = """ @@ -74,21 +82,6 @@ def print_last_line(): {setup} """ -# Max to display in a codeblock before sending to a paste service -# This also applies to text files -MAX_OUTPUT_BLOCK_LINES = 10 -MAX_OUTPUT_BLOCK_CHARS = 1000 - -# The Snekbox commands' whitelists and blacklists. -NO_SNEKBOX_CHANNELS = (Channels.python_general,) -NO_SNEKBOX_CATEGORIES = () -SNEKBOX_ROLES = (Roles.helpers, Roles.moderators, Roles.admins, Roles.owners, Roles.python_community, Roles.partners) - -REDO_EMOJI = "\U0001f501" # :repeat: -REDO_TIMEOUT = 30 - -SupportedPythonVersions = Literal["3.14", "3.13", "3.13t"] - class FilteredFiles(NamedTuple): allowed: list[FileAttachment] blocked: list[FileAttachment] diff --git a/bot/exts/utils/snekbox/_constants.py b/bot/exts/utils/snekbox/_constants.py new file mode 100644 index 0000000000..efcb47c33b --- /dev/null +++ b/bot/exts/utils/snekbox/_constants.py @@ -0,0 +1,22 @@ +import re +from typing import Literal + +from bot.constants import Channels, Roles + +ANSI_REGEX = re.compile(r"\N{ESC}\[[0-9;:]*m") +ESCAPE_REGEX = re.compile("[`\u202E\u200B]{3,}") + +# Max to display in a codeblock before sending to a paste service +# This also applies to text files +MAX_OUTPUT_BLOCK_LINES = 10 +MAX_OUTPUT_BLOCK_CHARS = 1000 + +# The Snekbox commands' whitelists and blacklists. +NO_SNEKBOX_CHANNELS = (Channels.python_general,) +NO_SNEKBOX_CATEGORIES = () +SNEKBOX_ROLES = (Roles.helpers, Roles.moderators, Roles.admins, Roles.owners, Roles.python_community, Roles.partners) + +REDO_EMOJI = "\U0001f501" # :repeat: +REDO_TIMEOUT = 30 + +SupportedPythonVersions = Literal["3.14", "3.13", "3.13t"] From 4ff670c35c92cea667a2872e63828f90e837cd53 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 14:46:08 +0100 Subject: [PATCH 4/7] Update EvalJob to fetch default Python version instead of hardcoding --- bot/exts/utils/snekbox/_eval.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bot/exts/utils/snekbox/_eval.py b/bot/exts/utils/snekbox/_eval.py index 3d2dd3201b..73f843bc0e 100644 --- a/bot/exts/utils/snekbox/_eval.py +++ b/bot/exts/utils/snekbox/_eval.py @@ -3,17 +3,15 @@ import contextlib from dataclasses import dataclass, field from signal import Signals -from typing import TYPE_CHECKING +from typing import get_args from discord.utils import escape_markdown, escape_mentions from bot.constants import Emojis +from bot.exts.utils.snekbox._constants import SupportedPythonVersions from bot.exts.utils.snekbox._io import FILE_COUNT_LIMIT, FILE_SIZE_LIMIT, FileAttachment, sizeof_fmt from bot.log import get_logger -if TYPE_CHECKING: - from bot.exts.utils.snekbox._cog import SupportedPythonVersions - log = get_logger(__name__) SIGKILL = 9 @@ -26,7 +24,7 @@ class EvalJob: args: list[str] files: list[FileAttachment] = field(default_factory=list) name: str = "eval" - version: SupportedPythonVersions = "3.13" + version: SupportedPythonVersions = get_args(SupportedPythonVersions)[0] @classmethod def from_code(cls, code: str, path: str = "main.py") -> EvalJob: From 92a0fb8717862913782ae6e76c47961d052aa4b5 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 17:51:37 +0100 Subject: [PATCH 5/7] Stop using Literal ordering to determine default !eval Python version Instead of using typing.get_args(...) and fetching the first value returned (which is mostly safe, but not guaranteed to be so), use a new constant DEFAULT_PYTHON_VERSION to control the current default Python executor when no explicit interpreter version is passed. --- bot/exts/utils/snekbox/_cog.py | 5 +++-- bot/exts/utils/snekbox/_constants.py | 4 +++- bot/exts/utils/snekbox/_eval.py | 5 ++--- tests/bot/exts/utils/snekbox/test_snekbox.py | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bot/exts/utils/snekbox/_cog.py b/bot/exts/utils/snekbox/_cog.py index 5fb160a5f2..b6825faa73 100644 --- a/bot/exts/utils/snekbox/_cog.py +++ b/bot/exts/utils/snekbox/_cog.py @@ -20,6 +20,7 @@ from bot.exts.help_channels._channel import is_help_forum_post from bot.exts.utils.snekbox._constants import ( ANSI_REGEX, + DEFAULT_PYTHON_VERSION, ESCAPE_REGEX, MAX_OUTPUT_BLOCK_CHARS, MAX_OUTPUT_BLOCK_LINES, @@ -602,7 +603,7 @@ async def eval_command( ) -> None: """Run Python code and get the results.""" code: list[str] - python_version = python_version or get_args(SupportedPythonVersions)[0] + python_version = python_version or DEFAULT_PYTHON_VERSION job = EvalJob.from_code("\n".join(code)).as_version(python_version) await self.run_job(ctx, job) @@ -643,7 +644,7 @@ async def timeit_command( ) -> None: """Profile Python Code to find execution time.""" code: list[str] - python_version = python_version or get_args(SupportedPythonVersions)[0] + python_version = python_version or DEFAULT_PYTHON_VERSION args = self.prepare_timeit_input(code) job = EvalJob(args, version=python_version, name="timeit") diff --git a/bot/exts/utils/snekbox/_constants.py b/bot/exts/utils/snekbox/_constants.py index efcb47c33b..09f94170bf 100644 --- a/bot/exts/utils/snekbox/_constants.py +++ b/bot/exts/utils/snekbox/_constants.py @@ -19,4 +19,6 @@ REDO_EMOJI = "\U0001f501" # :repeat: REDO_TIMEOUT = 30 -SupportedPythonVersions = Literal["3.14", "3.13", "3.13t"] +SupportedPythonVersions = Literal["3.13", "3.13t", "3.14"] + +DEFAULT_PYTHON_VERSION: SupportedPythonVersions = "3.14" diff --git a/bot/exts/utils/snekbox/_eval.py b/bot/exts/utils/snekbox/_eval.py index 73f843bc0e..6240d0cc6c 100644 --- a/bot/exts/utils/snekbox/_eval.py +++ b/bot/exts/utils/snekbox/_eval.py @@ -3,12 +3,11 @@ import contextlib from dataclasses import dataclass, field from signal import Signals -from typing import get_args from discord.utils import escape_markdown, escape_mentions from bot.constants import Emojis -from bot.exts.utils.snekbox._constants import SupportedPythonVersions +from bot.exts.utils.snekbox._constants import DEFAULT_PYTHON_VERSION, SupportedPythonVersions from bot.exts.utils.snekbox._io import FILE_COUNT_LIMIT, FILE_SIZE_LIMIT, FileAttachment, sizeof_fmt from bot.log import get_logger @@ -24,7 +23,7 @@ class EvalJob: args: list[str] files: list[FileAttachment] = field(default_factory=list) name: str = "eval" - version: SupportedPythonVersions = get_args(SupportedPythonVersions)[0] + version: SupportedPythonVersions = DEFAULT_PYTHON_VERSION @classmethod def from_code(cls, code: str, path: str = "main.py") -> EvalJob: diff --git a/tests/bot/exts/utils/snekbox/test_snekbox.py b/tests/bot/exts/utils/snekbox/test_snekbox.py index 69262bf615..7a0a703858 100644 --- a/tests/bot/exts/utils/snekbox/test_snekbox.py +++ b/tests/bot/exts/utils/snekbox/test_snekbox.py @@ -1,7 +1,6 @@ import asyncio import unittest from base64 import b64encode -from typing import get_args from unittest.mock import AsyncMock, MagicMock, Mock, call, create_autospec, patch from discord import AllowedMentions @@ -11,7 +10,8 @@ from bot import constants from bot.errors import LockedResourceError from bot.exts.utils import snekbox -from bot.exts.utils.snekbox import EvalJob, EvalResult, Snekbox, SupportedPythonVersions +from bot.exts.utils.snekbox import EvalJob, EvalResult, Snekbox +from bot.exts.utils.snekbox._constants import DEFAULT_PYTHON_VERSION from bot.exts.utils.snekbox._io import FileAttachment from tests.helpers import MockBot, MockContext, MockMember, MockMessage, MockReaction, MockUser @@ -22,7 +22,7 @@ def setUp(self): self.bot = MockBot() self.cog = Snekbox(bot=self.bot) self.job = EvalJob.from_code("import random") - self.default_version = get_args(SupportedPythonVersions)[0] + self.default_version = DEFAULT_PYTHON_VERSION @staticmethod def code_args(code: str) -> tuple[EvalJob]: From 784630351f06f635bfef44400a129435e8592c2b Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 9 Oct 2025 17:55:22 +0100 Subject: [PATCH 6/7] Use last character of Python version to determine free threading Removes the need to hardcode each specific "3.Xt" version we may support in future. Co-authored-by: ChrisJL --- bot/exts/utils/snekbox/_eval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/snekbox/_eval.py b/bot/exts/utils/snekbox/_eval.py index 6240d0cc6c..1c107caaca 100644 --- a/bot/exts/utils/snekbox/_eval.py +++ b/bot/exts/utils/snekbox/_eval.py @@ -141,7 +141,7 @@ def get_failed_files_str(self, char_max: int = 85) -> str: def get_status_message(self, job: EvalJob) -> str: """Return a user-friendly message corresponding to the process's return code.""" - if job.version == "3.13t": + if job.version[-1] == "t": version_text = job.version.replace("t", " [free threaded]()") else: version_text = job.version From b2714a4fa3f850d170f4ad8fd4e7b74c39c99849 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Fri, 10 Oct 2025 19:20:14 +0100 Subject: [PATCH 7/7] Update 3.13t to 3.14t See python-discord/snekbox#246 for relevant snekbox changes --- bot/exts/utils/snekbox/_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/utils/snekbox/_constants.py b/bot/exts/utils/snekbox/_constants.py index 09f94170bf..ca4376b5f5 100644 --- a/bot/exts/utils/snekbox/_constants.py +++ b/bot/exts/utils/snekbox/_constants.py @@ -19,6 +19,6 @@ REDO_EMOJI = "\U0001f501" # :repeat: REDO_TIMEOUT = 30 -SupportedPythonVersions = Literal["3.13", "3.13t", "3.14"] +SupportedPythonVersions = Literal["3.13", "3.14", "3.14t"] DEFAULT_PYTHON_VERSION: SupportedPythonVersions = "3.14"