Skip to content

Allow Evaluation Using Multiple Python Versions #158

Open

Description

@HassanAbouelela

Description

Install multiple python versions within the container, and add an option in the API to select between them.

Rationale

Currently, the bot supports evaluation of both 3.10 and 3.11 code, something I think should be kept and expanded in the future (to include major/important python versions). This is achieved by using the latest 3.10 image published here. This is fine for now, but it makes it harder to provide feature parity and avoid issues when users switch between modes, and it makes it difficult to patch and fix security issues.

Implementation

The way I see us doing this is by building all the versions of python we want to support directly into the image, then aiming the jail at the right executable. This would simplify implementation by having one server, with one codebase, while allowing us to evaluate user code on any version.

Specifics

Some more specific implementation details that can be up for debate:

  1. The docker image will simply install whatever versions of python you pass to it in the build arg, to well defined locations
  2. We'll build two images in CI, one with all versions we want supported, and one lightweight for dev
  3. We can expose an endpoint to get all supported versions
  4. We should investigate the best way to install all the versions to the docker image. I imagine the best solution is to just start from an image with all the required build-tools, install all the python versions, and use that as the base for the image. I found this docker image which has all the versions from 2.3-3.9 installed, and it comes out to a whooping... 600 MBs! I don't think this specific project would work for us since it hasn't been updated since 3.9, but it does make the idea seem feasiable.
  5. Are the exposed packages going to be an issue, or can we simply install all the same versions to all python interpreters.

Activity

  1. onerandomusername commented on Nov 20, 2022

    @onerandomusername
    Contributor

    Are the exposed packages going to be an issue, or can we simply install all the same versions to all python interpreters.

    I think it would be a nice idea to be able to have different packages installed. Any c-extension could compile differently based on the targeted python version, so it would be nice to have different versions.

  2. ChrisLovering commented on Oct 3, 2023

    @ChrisLovering
    Member

    The snekbox Dockerfile now installs both 3.12.0 and 3.13-dev to /lang/python/<version>.
    There is also /lang/python/default which is a symlink to /lang/python/3.12/ which is what snekbox currently uses for eval.

    Now we need to update snekbox's API to support multiple versions.
    The following is my opinion on how to do that.

    A new endpoint GET /versions is added, which returns all versions snekbox supports. The response body would include the name, a human friendly name, and whether it is a default. this would allow clients to easily give their users a list of versions that they can use.

    POST /eval will be updated to have an optional version key, which is the version to eval the code under. This value of this key would be the name field from GET /versions. If it is not given, then the version marked as default in GET /versions is used instead.

  3. MarkKoz commented on Oct 4, 2023

    @MarkKoz
    Contributor

    @ChrisLovering This was essentially the approach taken in #175, which has some discussion.

    I think it is better to accept an arbitrary path to a binary to execute. I'm hesitant to make design decisions that pigeon-hole snekbox into just a Python evaluator or just a code evaluator in general. Technically there is not much difference between that and sandboxing arbitrary binaries.

    On the other hand, that is the common use case, so we can compromise here. I don't see a strong need for exposing versions via an API, but if we were to do it, I think it should return the paths to the binaries, so they can be passed to the eval endpoint.

  4. onerandomusername commented on Oct 17, 2025

    @onerandomusername
    Contributor

    Noticing there's not further progress on this, but it seems that snekbox supports this, just undocumented. What do we need to do to move forward on this?

  5. jb3 commented on Oct 17, 2025

    @jb3
    Member

    Noticing there's not further progress on this, but it seems that snekbox supports this, just undocumented. What do we need to do to move forward on this?

    There are ongoing internal discussions about this that probably need condensing into a well-formed public issue at some point.

    Step 1 is complete, we do build additional interpreters in the snekbox image. We do this in a slightly different order to the initially proposed order (we build interpreters first and then build snekbox on top of that). I believe the reason we do this is probably for caching (building new interpreters is expensive, and if we can avoid changing those layers it's better).

    This is partially documented, though not in the best places. We do document how to add new interpreters here: https://github.com/python-discord/snekbox/blob/main/.github/CONTRIBUTING.md#adding-and-updating-python-interpreters

    Tasks still needed are:

    • Document /snekbin/ and the executable_path body parameter to the /eval endpoint
    • Decide on how we want to separate snekbox from Python executable building (basically where the line gets drawn between snekbox as an app and pydis as a snekbox consumer)
    • We need to decide how/if we are exposing an executable list to end users. This is also something we've discussed at length internally and the consensus seems to be returning a list of the /snekbin/ files whilst supporting symlinks from there to prevent accidental restriction of functionality.

    The idea with that last point is that the bot no longer needs updating when we change Python versions in snekbox but that's potentially something we can close this issue without solving.

    The initial comment about exposed packages has proved to be mostly a non-issue as we just use version specifiers and update which packages are available in which interpreter environment as and when they become compatible.

    The TL;DR is that we do support this issue 90% but I'm not sure the way we currently do this is the best, it certainly makes snekbox a lot less attractive to external users now that we bundle a bunch of other interpreter versions in our distributed images.

  6. onerandomusername commented on Oct 17, 2025

    @onerandomusername
    Contributor

    Decide on how we want to separate snekbox from Python executable building (basically where the line gets drawn between snekbox as an app and pydis as a snekbox consumer)

    As cool as snekbox is, it is literally named snekbox: snake: python. This name sort of locks into being a python code runner rather than anything else. Plus, the endpoint is at /eval, at the root.

    Changing scope would require updating and migrating the API in a backwards compatiable manner.

    As for building interpreters, they can probably be installed with python build standalone, at least for the "mainstream" ones (jit would need to be built somewhere)

  7. jb3 commented on Oct 17, 2025

    @jb3
    Member

    As cool as snekbox is, it is literally named snekbox: snake: python. This name sort of locks into being a python code runner rather than anything else. Plus, the endpoint is at /eval, at the root.

    This is a little bit ridiculous and I disagree completely, I think that locking project scopes based on names is silly (unless they are descriptive names like python-eval-api or something). Snekbox can be whatever we want and we actively have had ideas internally and in #dev-contrib before about opening up other options for !eval (things like allowing an !eval version for things like bash).

    We named it because of who made it and it was an amusing name.

    Changing scope would require updating and migrating the API in a backwards compatiable manner.

    We aren't changing scope. We had to amend snekbox to support multiple python versions and would have to amend it again to support wider more flexible eval options (like bash or whatever).

    As for building interpreters, they can probably be installed with python build standalone, at least for the "mainstream" ones (jit would need to be built somewhere)

    Our current build solution works fine, we probably just need to rearrange things into the following:

    • One base snekbox image that has no Python versions included, as generic as we can make it.
    • A set of other pre-built and regularly updated images from another repo that host the outputs of the ./python_build.sh script (see how we currently COPY built Python versions)
    • A final PyDis specific image that has the snekbox base along with some COPY --from options to copy built Python versions out of pre-built and cacheable images.

    All of this is based on what we already have, based on our existing build Dockerfile and is more a rearrangement and separation of steps rather than a major refactor.

    Additionally, since we never pushed snekbox far and wide we don't need to be hard backwards-compatible. We don't have versions of snekbox (but these changes would make it much nicer and easier to move towards having proper versioning for snekbox base).

  8. onerandomusername commented on Oct 17, 2025

    @onerandomusername
    Contributor

    (edit: wrote this before the above reply)

    Here are my thoughts, disregard if its not helpful.

    Details

    We need to decide how/if we are exposing an executable list to end users. This is also something we've discussed at length internally and the consensus seems to be returning a list of the /snekbin/ files whilst supporting symlinks from there to prevent accidental restriction of functionality.

    My thoughts are as follows

    Deprecate /eval, replace with the following:
    /python
    /python/exec/[version]

    add /exec which can use a specific binary, basically the current /eval functionality minus the python specific endpoints.

    For not bundling python: Add support for inside (os path) /snekbin where a user can add a volume containing python binaries. That folder contains an additional versions.json file which includes an object mapping of python version to which binary in there is for that version. This versions.json file should be complete with the patch version, eg 3.13.6t for 3.13.6 free threading.

    If that folder is not found, /python returns an empty list, and /python/exec/[version] returns the same as if an invalid version is found.

    This theoretical new api would look like the following:

    POST /exec -> run with specified binary, no defaults
    POST /eval -> deprecated, now undocumented
    GET /python -> supported python versions
    POST /python/exec -> run python code, akin to the existing /eval endpoint but without {"input":...} support
    GET /exec -> list the binaries in /snekbin which are the supported versions

  9. jb3 commented on Oct 17, 2025

    @jb3
    Member

    This seems overcomplicated. My mental construction of how this looks is a lot simpler.

    • Store binaries such as python-3.14j, python-3.14 in /snekbin/ (as we currently do), these can be added by a new Docker stage that builds after the base (instead of before like we currently have, see the end of my above comment)
    • Return a list of /snekbin/ contents via a new API endpoint, confirm they have execute bit set, the symlink destinations exist, etc.
    • Consumers can then just match based on what they want. It seems reasonable to have users say "Ah, I need Python versions, what matches python-*, that is what I will show the user" (and as an example is what Python bot would grab on start-up/when refreshed by an admin)
    • All consumers then have to do is use the existing executable_path

    If users want to do higher level orchestrating of mapping versions to binaries that can be handled externally by their own bespoke solution/JSON file/etc, but this doesn't need to be more complex than returning a list of files.

    For this specific issue, the above is all that is really needed to close this issue and the wider things can come as new issues down the line if we decide to proceed that way.

  10. onerandomusername commented on Oct 17, 2025

    @onerandomusername
    Contributor

    That makes sense. I do like the idea of python being specially handled, but its not a requirement.

  11. jb3 commented on Oct 17, 2025

    @jb3
    Member

    I think if we are trying to generalise then keeping specific functionality is something we need to carefully consider, and adding an endpoint of /python/exec/3.14 that just maps into /snekbin/python-3.14 doesn't seem like it's worth keeping a specific Python thing.

    The client already has to know the version to call that endpoint, and having it as a path-param vs a generic backwards compatible body parameter doesn't give a huge benefit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: backendRelated to internal functionality and utilitiesstatus: planningDiscussing detailstype: featureNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions