Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
69 changes: 67 additions & 2 deletions lib/pythonx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,23 @@ defmodule Pythonx do

@moduledoc readme_docs

defstruct [
:python_dl_path,
:python_home_path,
:python_executable_path,
:sys_paths
]

alias Pythonx.Object

@install_env_name "PYTHONX_FLAME_INIT_STATE"

@type init_state :: %__MODULE__{
python_dl_path: String.t(),
python_home_path: String.t(),
python_executable_path: String.t(),
sys_paths: [String.t()]
}
@type encoder :: (term(), encoder() -> Object.t())

@doc ~s'''
Expand Down Expand Up @@ -58,7 +73,47 @@ defmodule Pythonx do
opts = Keyword.validate!(opts, force: false, uv_version: Pythonx.Uv.default_uv_version())

Pythonx.Uv.fetch(pyproject_toml, false, opts)
Pythonx.Uv.init(pyproject_toml, false, Keyword.take(opts, [:uv_version]))
init_state = Pythonx.Uv.init(pyproject_toml, false, Keyword.take(opts, [:uv_version]))
:persistent_term.put(:pythonx_init_state, init_state)
end

@spec init_state() :: init_state()
defp init_state() do
:persistent_term.get(:pythonx_init_state)
end

@doc ~s'''
Fetches the pythonx init state from the system environment variable.
'''
@spec init_state_from_env() :: String.t() | nil
def init_state_from_env(), do: System.get_env(@install_env_name)

@doc ~s'''
Returns a map containing the environment variables required to initialize Pythonx.
'''
@spec install_env() :: map()
def install_env() do
init_state =
init_state()
|> :erlang.term_to_binary()
|> Base.encode64()

%{name: @install_env_name, value: init_state}
end

@doc ~s'''
Returns a list of paths to copy to the flame runner.
'''
@spec install_paths() :: list(String.t())
def install_paths() do
init_state = init_state()

[
init_state.python_dl_path,
init_state.python_executable_path
] ++
init_state.sys_paths ++
Path.wildcard(Path.join(init_state.python_home_path, "**"), match_dot: true)
end

# Initializes the Python interpreter.
Expand Down Expand Up @@ -90,7 +145,7 @@ defmodule Pythonx do
# (`sys.path`). Defaults to `[]`.
#
@doc false
@spec init(String.t(), String.t(), keyword()) :: :ok
@spec init(String.t(), String.t(), String.t(), keyword()) :: :ok
def init(python_dl_path, python_home_path, python_executable_path, opts \\ [])
when is_binary(python_dl_path) and is_binary(python_home_path)
when is_binary(python_executable_path) and is_list(opts) do
Expand All @@ -111,6 +166,16 @@ defmodule Pythonx do
Pythonx.NIF.init(python_dl_path, python_home_path, python_executable_path, opts[:sys_paths])
end

@spec init(init_state()) :: :ok
def init(%__MODULE__{
python_dl_path: python_dl_path,
python_home_path: python_home_path,
python_executable_path: python_executable_path,
sys_paths: sys_paths
}) do
init(python_dl_path, python_home_path, python_executable_path, sys_paths: sys_paths)
end

@doc ~S'''
Evaluates the Python `code`.

Expand Down
13 changes: 12 additions & 1 deletion lib/pythonx/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,18 @@ defmodule Pythonx.Application do
Pythonx.Uv.fetch(pyproject_toml, true, opts)
defp maybe_uv_init(), do: Pythonx.Uv.init(unquote(pyproject_toml), true, unquote(opts))
else
defp maybe_uv_init(), do: :noop
defp maybe_uv_init() do
case Pythonx.init_state_from_env() do
nil ->
:noop

init_state_env_value ->
init_state_env_value
|> Base.decode64!()
|> :erlang.binary_to_term()
|> Pythonx.init()
end
end
end

defp enable_sigchld() do
Expand Down
118 changes: 64 additions & 54 deletions lib/pythonx/uv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ defmodule Pythonx.Uv do
Initializes the interpreter using Python and dependencies previously
fetched by `fetch/3`.
"""
@spec init(String.t(), boolean()) :: :ok
@spec init(String.t(), boolean()) :: Pythonx.init_state()
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])
Expand Down Expand Up @@ -95,59 +95,69 @@ defmodule Pythonx.Uv do

root_dir = Path.join(python_install_dir(priv?, opts[:uv_version]), versioned_dir_name)

case :os.type() do
{:win32, _osname} ->
# Note that we want the version-specific DLL, rather than the
# "forwarder DLL" python3.dll, otherwise symbols cannot be
# found directly.
python_dl_path =
root_dir
|> Path.join("python3?*.dll")
|> wildcard_one!()
|> make_windows_slashes()

python_home_path = make_windows_slashes(root_dir)

python_executable_path =
project_dir
|> Path.join(".venv/Scripts/python.exe")
|> make_windows_slashes()

venv_packages_path =
project_dir
|> Path.join(".venv/Lib/site-packages")
|> make_windows_slashes()

Pythonx.init(python_dl_path, python_home_path, python_executable_path,
sys_paths: [venv_packages_path]
)

{:unix, osname} ->
dl_extension =
case osname do
:darwin -> ".dylib"
:linux -> ".so"
end

python_dl_path =
root_dir
|> Path.join("lib/libpython3.*" <> dl_extension)
|> wildcard_one!()
|> Path.expand()

python_home_path = root_dir

python_executable_path = Path.join(project_dir, ".venv/bin/python")

venv_packages_path =
project_dir
|> Path.join(".venv/lib/python3*/site-packages")
|> wildcard_one!()

Pythonx.init(python_dl_path, python_home_path, python_executable_path,
sys_paths: [venv_packages_path]
)
end
init_state =
case :os.type() do
{:win32, _osname} ->
# Note that we want the version-specific DLL, rather than the
# "forwarder DLL" python3.dll, otherwise symbols cannot be
# found directly.
python_dl_path =
root_dir
|> Path.join("python3?*.dll")
|> wildcard_one!()
|> make_windows_slashes()

python_home_path = make_windows_slashes(root_dir)

python_executable_path =
project_dir
|> Path.join(".venv/Scripts/python.exe")
|> make_windows_slashes()

venv_packages_path =
project_dir
|> Path.join(".venv/Lib/site-packages")
|> make_windows_slashes()

%Pythonx{
python_dl_path: python_dl_path,
python_home_path: python_home_path,
python_executable_path: python_executable_path,
sys_paths: [venv_packages_path]
}

{:unix, osname} ->
dl_extension =
case osname do
:darwin -> ".dylib"
:linux -> ".so"
end

python_dl_path =
root_dir
|> Path.join("lib/libpython3.*" <> dl_extension)
|> wildcard_one!()
|> Path.expand()

python_home_path = root_dir

python_executable_path = Path.join(project_dir, ".venv/bin/python")

venv_packages_path =
project_dir
|> Path.join(".venv/lib/python3*/site-packages")
|> wildcard_one!()

%Pythonx{
python_dl_path: python_dl_path,
python_home_path: python_home_path,
python_executable_path: python_executable_path,
sys_paths: [venv_packages_path]
}
end

Pythonx.init(init_state)
init_state
end

defp wildcard_one!(path) do
Expand Down