diff --git a/docs/src/faq.md b/docs/src/faq.md index 76e268fe..c36e4aab 100644 --- a/docs/src/faq.md +++ b/docs/src/faq.md @@ -1,6 +1,6 @@ # FAQ & Troubleshooting -## Can I use PythonCall and PyCall together? +## [Can I use PythonCall and PyCall together?](@id faq-pycall) Yes, you can use both PyCall and PythonCall in the same Julia session. This is platform-dependent: - On most systems the Python interpreter used by PythonCall and PyCall must be the same (see below). diff --git a/docs/src/pythoncall.md b/docs/src/pythoncall.md index 4f67269a..a4f191e8 100644 --- a/docs/src/pythoncall.md +++ b/docs/src/pythoncall.md @@ -240,6 +240,7 @@ to your current Julia project containing Python and any required Python packages ENV["JULIA_CONDAPKG_BACKEND"] = "Null" ENV["JULIA_PYTHONCALL_EXE"] = "/path/to/python" # optional ENV["JULIA_PYTHONCALL_EXE"] = "@PyCall" # optional +ENV["JULIA_PYTHONCALL_EXE"] = "@venv" # optional ``` By setting the CondaPkg backend to Null, it will never install any Conda packages. In this @@ -247,10 +248,13 @@ case, PythonCall will use whichever Python is currently installed and in your `P must have already installed any Python packages that you need. If `python` is not in your `PATH`, you will also need to set `JULIA_PYTHONCALL_EXE` to its -path. +path. Relative paths are resolved relative to the current active project. If you also use PyCall, you can set `JULIA_PYTHONCALL_EXE=@PyCall` to use the same Python -interpreter. +interpreter. [See here](@ref faq-pycall). + +If you have a Python virtual environment at `.venv` in your current active project, you +can set `JULIA_PYTHONCALL_EXE=@venv` to use it. #### If you already have a Conda environment diff --git a/docs/src/releasenotes.md b/docs/src/releasenotes.md index 7a9f1b65..996aaf13 100644 --- a/docs/src/releasenotes.md +++ b/docs/src/releasenotes.md @@ -5,6 +5,8 @@ * Added `pyconvert` rules for NumpyDates types. * Added `PyArray` support for NumPy arrays of `datetime64` and `timedelta64`. * Added `juliacall.ArrayValue` support for Julia arrays of `InlineDateTime64` and `InlineTimeDelta64`. +* If `JULIA_PYTHONCALL_EXE` is a relative path, it is now considered relative to the active project. +* Added option `JULIA_PYTHONCALL_EXE=@venv` to use a Python virtual environment relative to the active project. * Bug fixes. * Internal: switch from Requires.jl to package extensions. diff --git a/src/C/context.jl b/src/C/context.jl index 5d3bf92e..8d2714c5 100644 --- a/src/C/context.jl +++ b/src/C/context.jl @@ -75,14 +75,38 @@ function init_context() exe_path = PyCall.python::String CTX.lib_path = PyCall.libpython::String CTX.which = :PyCall + elseif exe_path == "@venv" + # load from a .venv in the active project + exe_path = abspath(dirname(Base.active_project()), ".venv") + if Sys.iswindows() + exe_path = abspath(exe_path, "Scripts", "python.exe")::String + else + exe_path = abspath(exe_path, "bin", "python")::String + end elseif startswith(exe_path, "@") error("invalid JULIA_PYTHONCALL_EXE=$exe_path") else # Otherwise we use the Python specified CTX.which = :unknown + if isabspath(exe_path) + # nothing to do + elseif '/' in exe_path || '\\' in exe_path + # it's a relative path, interpret it as relative to the current project + exe_path = abspath(dirname(Base.active_project()), exe_path)::String + else + # it's a command, find it in the PATH + given_exe_path = exe_path + exe_path = Sys.which(exe_path) + exe_path === nothing && + error("Python executable $(repr(given_exe_path)) not found.") + exe_path::String + end end # Ensure Python is runnable + if !ispath(exe_path) + error("Python executable $(repr(exe_path)) does not exist.") + end try run(pipeline(`$exe_path --version`, stdout = devnull, stderr = devnull)) catch