Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/pythonx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,26 @@ 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. 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
def uv_init(pyproject_toml, opts \\ []) when is_binary(pyproject_toml) and is_list(opts) do
opts =
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,
Expand Down
21 changes: 14 additions & 7 deletions lib/pythonx/uv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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)

Expand All @@ -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,
Expand Down