python_executable

TODO: Add separate FS for the concept of StateStride.

Intro

The stand-alone proto_code is designed to be run by any python executable (available in PATH).

Eventually, based on the target client repo requirements, it must become:

  • the required python version

  • executed from the initialized venv (with all dependencies)

To achieve this, protoprimer switches python executables in a multi-stage bootstrap sequence:

./prime -v

Each executable is replaced with an os.execve call.

Details

That initial executable is unpredictable:

  • It may be any in the user PATH env var (via shebang_line).

  • It may be any specified by the user directly.

Moreover, once venv is initialized, proto_code also needs to switch to required python inside that venv to install dependencies there (see primer_runtime).

proto_primer switches progressively to next python binary (starts a new process) communicating that progress via EnvVar.var_PROTOPRIMER_PY_EXEC.

Required python: selecting executable path

Required python version is specified per environment via the config field required_python_version.

If the field is not specified, .python-version file is searched up from ref root dir (e.g. repo root).

Depending on the tools (e.g. uv, pip, ...) the required python version is installed or only validated.

python: selected vs required

There are two python adjectives in use:

  • selected (about executable):

    protoprimer implements python executable (abs path) selection process.

  • required (about version):

    protoprimer allows specifying required python version.

Depending on config, the selected python executable does not necessarily match the required python version.

However, using uv guarantees that selected python executable matches required python version.

python selector

There can be many ways to select specific python executable paths in various target environments:

  • use some environment-specific absolute path

  • use some environment manager to configure required python

  • ...

protoprimer delegates that selection to python selectors.

python selector is a stand-alone script configured in python_selector_file_rel_path.

If python selector is not specified, protoprimer handles the basic (see below) by trying python basenames.

python selector with uv

If uv is used, the selected python is mostly irrelevant as it is only used to deploy uv into a temporary venv.

Such selected python must be >= "3.8" for uv to deploy successfully.

Subsequently, uv is used to create the dedicated venv which guarantees required python version.

Selecting python executable

The selection follows this pseudo-code:

def create_venv(venv_driver, current_python, required_version):
    if str(venv_driver) == "venv_uv":
        selected_python = probe_python(required_version)
        if selected_python:
            if what_version(selected_python) >= (3, 8):
                # NOTE: `required_python` has `required_version`:
                required_python = create_venv_by_uv(selected_python, required_version)
            else:
                required_python = None
        else:
            required_python = None
        return required_python
    else:
        selected_python = probe_python(required_version)
        if selected_python:
            # NOTE: `selected_python` may not have `required_version`:
            selected_python = create_venv_by_pip(selected_python, required_version)
        else:
            selected_python = None
        return selected_python

def probe_python(required_version):
    some_python = select_python()
    if some_python is None:
        some_python = search_python(required_version)
    return some_python

def create_venv_by_uv(given_python, required_version):
    # Use `what_version(given_python)` to create an intermediate `venv` for `uv`,
    # then install `uv` there,
    # then use `uv` to install `python` of `required_version`,
    # then create the final `venv` which uses `python` of `required_version`.
    pass

def create_venv_by_pip(given_python, required_version):
    # Verify that `what_version(given_python)` == `required_version`.
    # Create standard `venv` via CLI using `what_path(given_python)`.
    pass

def select_python():
    # Run `python` selector script specified in `python_selector_file_rel_path`.
    pass

def search_python(python_version):
    # Use `python_version` string formatted as "X.Y.Z" to try each basename (in that order):
    # `pythonX.Y.Z`
    # `pythonX.Y`
    # `pythonX`
    # `python`
    # Return the first basename found in `PATH` (e.g. via `shutil.which(...)`)
    # which also succeeds when invoked with `--version` option.
    pass

def what_version(some_python):
    # Return version of `some_python`.
    pass

def what_path(some_python):
    # Return path of `some_python`.
    pass