From 35199e66d33242d7afb082b508bcf0a541154ec6 Mon Sep 17 00:00:00 2001 From: Niko Maroulis Date: Sat, 14 Feb 2026 13:29:05 -0500 Subject: [PATCH 1/2] Add :python option to Pythonx.uv_init/2 for free-threaded Python support Allow specifying the Python version passed to uv via --python, enabling users to request free-threaded Python builds (e.g., "3.14t"). The python option is included in the project cache key so different variants get separate cache directories. --- lib/pythonx.ex | 11 +++++++++-- lib/pythonx/uv.ex | 21 ++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/pythonx.ex b/lib/pythonx.ex index 2db4ced..e0baf90 100644 --- a/lib/pythonx.ex +++ b/lib/pythonx.ex @@ -65,6 +65,10 @@ defmodule Pythonx do of vendored rustls. This is useful in corporate environments where the system certificate store must be used. Defaults to `false`. + * `:python` - specifies the Python version to install, passed as `--python` to uv. + This can be used to request a free-threaded Python build (e.g., `"3.14t"`). + Defaults to `nil` (uses the version from `requires-python` in `pyproject.toml`). + ''' @spec uv_init(String.t(), keyword()) :: :ok def uv_init(pyproject_toml, opts \\ []) when is_binary(pyproject_toml) and is_list(opts) do @@ -72,11 +76,14 @@ defmodule Pythonx do Keyword.validate!(opts, force: false, uv_version: Pythonx.Uv.default_uv_version(), - native_tls: false + native_tls: false, + python: nil ) Pythonx.Uv.fetch(pyproject_toml, false, opts) - install_paths = Pythonx.Uv.init(pyproject_toml, false, Keyword.take(opts, [:uv_version])) + + install_paths = + Pythonx.Uv.init(pyproject_toml, false, Keyword.take(opts, [:uv_version, :python])) init_state = %{ type: :uv_init, diff --git a/lib/pythonx/uv.ex b/lib/pythonx/uv.ex index 8fb039e..d6cad97 100644 --- a/lib/pythonx/uv.ex +++ b/lib/pythonx/uv.ex @@ -11,9 +11,14 @@ defmodule Pythonx.Uv do @spec fetch(String.t(), boolean(), keyword()) :: :ok def fetch(pyproject_toml, priv?, opts \\ []) do opts = - Keyword.validate!(opts, force: false, uv_version: default_uv_version(), native_tls: false) - - project_dir = project_dir(pyproject_toml, priv?, opts[:uv_version]) + Keyword.validate!(opts, + force: false, + uv_version: default_uv_version(), + native_tls: false, + python: nil + ) + + project_dir = project_dir(pyproject_toml, priv?, opts[:uv_version], opts[:python]) python_install_dir = python_install_dir(priv?, opts[:uv_version]) if opts[:force] || priv? do @@ -30,6 +35,7 @@ defmodule Pythonx.Uv do # We always use uv-managed Python, so the paths are predictable. base_args = ["sync", "--managed-python", "--no-config"] + base_args = if opts[:python], do: base_args ++ ["--python", opts[:python]], else: base_args uv_args = if opts[:native_tls], do: base_args ++ ["--native-tls"], else: base_args if run!(uv_args, @@ -53,12 +59,13 @@ defmodule Pythonx.Uv do end end - defp project_dir(pyproject_toml, priv?, uv_version) do + defp project_dir(pyproject_toml, priv?, uv_version, python) do if priv? do Path.join(:code.priv_dir(:pythonx), "uv/project") else cache_id = - pyproject_toml + [pyproject_toml, python || ""] + |> IO.iodata_to_binary() |> :erlang.md5() |> Base.encode32(case: :lower, padding: false) @@ -72,8 +79,8 @@ defmodule Pythonx.Uv do """ @spec init(String.t(), boolean()) :: list(String.t()) def init(pyproject_toml, priv?, opts \\ []) do - opts = Keyword.validate!(opts, uv_version: default_uv_version()) - project_dir = project_dir(pyproject_toml, priv?, opts[:uv_version]) + opts = Keyword.validate!(opts, uv_version: default_uv_version(), python: nil) + project_dir = project_dir(pyproject_toml, priv?, opts[:uv_version], opts[:python]) # Uv stores Python installations in versioned directories in the # Python install dir. To find the versioned name for this project, From 7643f21ce538a620b48413ccde8051c6c04cc7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Mon, 16 Feb 2026 17:19:31 +0100 Subject: [PATCH 2/2] Apply suggestion from @jonatanklosko --- lib/pythonx.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pythonx.ex b/lib/pythonx.ex index e0baf90..8fd9c12 100644 --- a/lib/pythonx.ex +++ b/lib/pythonx.ex @@ -65,9 +65,10 @@ defmodule Pythonx do of vendored rustls. This is useful in corporate environments where the system certificate store must be used. Defaults to `false`. - * `:python` - specifies the Python version to install, passed as `--python` to uv. - This can be used to request a free-threaded Python build (e.g., `"3.14t"`). - Defaults to `nil` (uses the version from `requires-python` in `pyproject.toml`). + * `:python` - specifies the Python version to install. It is preferred to + specify the version in `pyproject_toml` via the `requires-python` field. + However, the `:python` option can be used to install a specific variant + of Python, such as a free-threaded Python build, for example `"3.14t"`. ''' @spec uv_init(String.t(), keyword()) :: :ok