Skip to content

Commit 4765695

Browse files
committed
Add support for @ENV as an exe preference
1 parent 777fac8 commit 4765695

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

‎docs/src/pythoncall.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,15 @@ special values:
309309
- `@CondaPkg`: Use Python from CondaPkg (the default).
310310
- `@PyCall`: Use the same Python as PyCall. [See here](@ref faq-pycall).
311311
- `@venv`: Use Python from a `.venv` virtual environment in the current active project.
312+
- `@ENV`: This value should only be set as the `exe` preference. When set,
313+
PythonCall will look up the `exe` value explicitly from the
314+
`JULIA_PYTHONCALL_EXE` environment variable, which _must_ be a valid path or
315+
command name (see below). The advantage of setting `@ENV` as a preference is
316+
that CondaPkg.jl will not be loaded, which saves on load time, while you still
317+
keep the ability to change Python environments based on environment
318+
variables. This can be helpful when using shared Julia environments on a
319+
cluster so that users can easily switch Python environments without modifying
320+
the depot preferences.
312321

313322
Otherwise, the value is interpreted as:
314323
- An absolute path to a Python executable.

‎src/C/context.jl‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,16 @@ function init_context()
169169
else
170170
exe_path = abspath(exe_path, "bin", "python")::String
171171
end
172-
elseif startswith(exe_path, "@")
172+
elseif startswith(exe_path, "@") && exe_path != "@ENV"
173173
error("invalid exe: $exe_path")
174174
else
175+
if exe_path == "@ENV"
176+
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
177+
if isempty(exe_path)
178+
error("PythonCall's `exe` preference is set to `@ENV`, but JULIA_PYTHONCALL_EXE is not set.")
179+
end
180+
end
181+
175182
# Otherwise we use the Python specified
176183
CTX.which = :unknown
177184
if isabspath(exe_path)

‎test/C.jl‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,31 @@
1616
@test PythonCall.python_version().major == 3
1717
end
1818
end
19+
20+
@testitem "exe = @ENV" begin
21+
mktempdir() do dir
22+
# A throwaway environment prepended to the load path overrides the `exe`
23+
# preference for a child process while still resolving PythonCall from ours.
24+
write(
25+
joinpath(dir, "Project.toml"),
26+
"[extras]\nPythonCall = \"6099a3de-0909-46bc-b1f4-468b9a2dfc0d\"\n",
27+
)
28+
write(joinpath(dir, "LocalPreferences.toml"), "[PythonCall]\nexe = \"@ENV\"\n")
29+
sep = Sys.iswindows() ? ";" : ":"
30+
loadpath = join([dir, Base.active_project(), "@stdlib"], sep)
31+
exe = PythonCall.python_executable_path()
32+
code = "using PythonCall; print(PythonCall.python_executable_path())"
33+
cmd = `$(Base.julia_cmd()) --startup-file=no -e $code`
34+
35+
# variable set: it is used as the executable
36+
withset = addenv(cmd, "JULIA_LOAD_PATH" => loadpath, "JULIA_PYTHONCALL_EXE" => exe)
37+
@test readchomp(withset) == exe
38+
39+
# variable unset: fails at load time with a clear message
40+
unset = addenv(cmd, "JULIA_LOAD_PATH" => loadpath, "JULIA_PYTHONCALL_EXE" => nothing)
41+
err = IOBuffer()
42+
proc = run(pipeline(ignorestatus(unset), stdout = devnull, stderr = err))
43+
@test !success(proc)
44+
@test occursin("JULIA_PYTHONCALL_EXE is not set", String(take!(err)))
45+
end
46+
end

0 commit comments

Comments
 (0)