Skip to content

Commit 38490df

Browse files
committed
add Preferences.jl-based configuration
1 parent ef6c404 commit 38490df

File tree

7 files changed

+93
-20
lines changed

7 files changed

+93
-20
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
1010
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
1111
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
1212
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
13+
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
1314
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
1415
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1516
UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
@@ -22,6 +23,7 @@ Libdl = "1"
2223
MacroTools = "0.5"
2324
Markdown = "1"
2425
Pkg = "1"
26+
Preferences = "1"
2527
PyCall = "1"
2628
Serialization = "1"
2729
Tables = "1"

docs/src/compat.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Whenever a Python exception is displayed by Julia, `sys.last_traceback` and frie
1313

1414
Python objects can be serialised with the [`Serialization`](https://docs.julialang.org/en/v1/stdlib/Serialization/) stdlib.
1515
This uses [`pickle`](https://docs.python.org/3/library/pickle.html) library under the hood.
16-
You can opt into using [`dill`](https://pypi.org/project/dill/) instead by setting the environment variable `JULIA_PYTHONCALL_PICKLE="dill"`.
16+
You can opt into using [`dill`](https://pypi.org/project/dill/) instead by setting the `pickle` preference or the `JULIA_PYTHONCALL_PICKLE` environment variable to `"dill"`.
1717

1818
## Tabular data / Pandas
1919

docs/src/pythoncall.md

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,35 +280,82 @@ By default, PythonCall uses [CondaPkg.jl](https://github.com/JuliaPy/CondaPkg.jl
280280
its dependencies. This will install Conda and use it to create a Conda environment specific
281281
to your current Julia project containing Python and any required Python packages.
282282

283-
#### If you already have Python and required Python packages installed
283+
### Preferences
284+
285+
You can configure PythonCall with the preferences listed below. These can be set either as
286+
[Julia preferences](https://github.com/JuliaPackaging/Preferences.jl) or as environment
287+
variables.
288+
289+
| Preference | Environment Variable | Description |
290+
| ---------- | -------------------- | ----------- |
291+
| `exe` | `JULIA_PYTHONCALL_EXE` | Path to the Python executable, or special values (see below). |
292+
| `lib` | `JULIA_PYTHONCALL_LIB` | Path to the Python library (usually inferred automatically). |
293+
| `pickle` | `JULIA_PYTHONCALL_PICKLE` | Pickle module to use for serialization (`pickle` or `dill`). |
294+
295+
The easiest way to set these preferences is with the
296+
[`PreferenceTools`](https://github.com/cjdoris/PreferenceTools.jl)
297+
package. For example:
298+
```
299+
julia> using PreferenceTools
300+
julia> # now press ] to enter the Pkg REPL
301+
pkg> preference add PythonCall exe=/path/to/python
302+
```
303+
304+
For CondaPkg preferences (such as `backend`, `exe`, `env`), see the
305+
[CondaPkg documentation](https://github.com/JuliaPy/CondaPkg.jl#preferences).
306+
307+
### Special `exe` values
308+
309+
The `exe` preference (or `JULIA_PYTHONCALL_EXE` environment variable) supports the following
310+
special values:
311+
- Empty string or `@CondaPkg`: Use Python from CondaPkg (the default).
312+
- `@PyCall`: Use the same Python as PyCall. [See here](@ref faq-pycall).
313+
- `@venv`: Use Python from a `.venv` virtual environment in the current active project.
314+
315+
Otherwise, the value is interpreted as:
316+
- An absolute path to a Python executable.
317+
- A relative path (containing `/` or `\`) resolved relative to the current active project.
318+
- A command name to search for in `PATH`.
319+
320+
### If you already have Python and required Python packages installed
284321

285322
```julia
286323
ENV["JULIA_CONDAPKG_BACKEND"] = "Null"
287324
ENV["JULIA_PYTHONCALL_EXE"] = "/path/to/python" # optional
288-
ENV["JULIA_PYTHONCALL_EXE"] = "@PyCall" # optional
289-
ENV["JULIA_PYTHONCALL_EXE"] = "@venv" # optional
325+
```
326+
327+
Or using preferences:
328+
```
329+
pkg> preference add CondaPkg backend=Null
330+
pkg> preference add PythonCall exe=/path/to/python # optional
290331
```
291332

292333
By setting the CondaPkg backend to Null, it will never install any Conda packages. In this
293334
case, PythonCall will use whichever Python is currently installed and in your `PATH`. You
294335
must have already installed any Python packages that you need.
295336

296-
If `python` is not in your `PATH`, you will also need to set `JULIA_PYTHONCALL_EXE` to its
297-
path. Relative paths are resolved relative to the current active project.
337+
If `python` is not in your `PATH`, you will also need to set `exe` (or `JULIA_PYTHONCALL_EXE`)
338+
to its path. Relative paths are resolved relative to the current active project.
298339

299-
If you also use PyCall, you can set `JULIA_PYTHONCALL_EXE=@PyCall` to use the same Python
300-
interpreter. [See here](@ref faq-pycall).
340+
If you also use PyCall, you can set `exe=@PyCall` to use the same Python interpreter.
341+
[See here](@ref faq-pycall).
301342

302343
If you have a Python virtual environment at `.venv` in your current active project, you
303-
can set `JULIA_PYTHONCALL_EXE=@venv` to use it.
344+
can set `exe=@venv` to use it.
304345

305-
#### If you already have a Conda environment
346+
### If you already have a Conda environment
306347

307348
```julia
308349
ENV["JULIA_CONDAPKG_BACKEND"] = "Current"
309350
ENV["JULIA_CONDAPKG_EXE"] = "/path/to/conda" # optional
310351
```
311352

353+
Or using preferences:
354+
```
355+
pkg> preference add CondaPkg backend=Current
356+
pkg> preference add CondaPkg exe=/path/to/conda # optional
357+
```
358+
312359
The Current backend to CondaPkg will use the currently activated Conda environment instead
313360
of creating a new one.
314361

@@ -317,23 +364,29 @@ If you already have your dependencies installed and do not want the environment
317364
modified, then see the previous section.
318365

319366
If `conda`, `mamba` or `micromamba` is not in your `PATH` you will also need to set
320-
`JULIA_CONDAPKG_EXE` to its path.
367+
`JULIA_CONDAPKG_EXE` (or the CondaPkg `exe` preference) to its path.
321368

322-
#### If you already have Conda, Mamba or MicroMamba
369+
### If you already have Conda, Mamba or MicroMamba
323370

324371
```julia
325372
ENV["JULIA_CONDAPKG_BACKEND"] = "System"
326373
ENV["JULIA_CONDAPKG_EXE"] = "/path/to/conda" # optional
327374
```
328375

376+
Or using preferences:
377+
```
378+
pkg> preference add CondaPkg backend=System
379+
pkg> preference add CondaPkg exe=/path/to/conda # optional
380+
```
381+
329382
The System backend to CondaPkg will use your preinstalled Conda implementation instead of
330383
downloading one.
331384

332385
Note that this will still create a new Conda environment and install any required packages
333386
into it. If you want to use a pre-existing Conda environment, see the previous section.
334387

335388
If `conda`, `mamba` or `micromamba` is not in your `PATH` you will also need to set
336-
`JULIA_CONDAPKG_EXE` to its path.
389+
`JULIA_CONDAPKG_EXE` (or the CondaPkg `exe` preference) to its path.
337390

338391
## [Installing Python packages](@id python-deps)
339392

src/C/C.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ using Libdl:
1414

1515
import ..PythonCall:
1616
python_executable_path, python_library_path, python_library_handle, python_version
17-
17+
using ..Utils: getpref_exe, getpref_lib
1818

1919
include("consts.jl")
2020
include("pointers.jl")

src/C/context.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,15 @@ function init_context()
114114
Py_IsInitialized() == 0 && error("Python is not already initialized.")
115115
CTX.is_initialized = true
116116
CTX.which = :embedded
117-
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
117+
exe_path = getpref_exe()
118118
if exe_path != ""
119119
CTX.exe_path = exe_path
120120
# this ensures PyCall uses the same Python interpreter
121121
get!(ENV, "PYTHON", exe_path)
122122
end
123123
else
124124
# Find Python executable
125-
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
125+
exe_path = getpref_exe()
126126
if exe_path == "" || exe_path == "@CondaPkg"
127127
if CondaPkg.backend() == :Null
128128
exe_path = Sys.which("python")
@@ -157,7 +157,7 @@ function init_context()
157157
exe_path = abspath(exe_path, "bin", "python")::String
158158
end
159159
elseif startswith(exe_path, "@")
160-
error("invalid JULIA_PYTHONCALL_EXE=$exe_path")
160+
error("invalid exe: $exe_path")
161161
else
162162
# Otherwise we use the Python specified
163163
CTX.which = :unknown
@@ -198,7 +198,7 @@ function init_context()
198198
# Find and open Python library
199199
lib_path = something(
200200
CTX.lib_path === missing ? nothing : CTX.lib_path,
201-
get(ENV, "JULIA_PYTHONCALL_LIB", nothing),
201+
getpref_lib(),
202202
Some(nothing),
203203
)
204204
if lib_path !== nothing
@@ -225,7 +225,7 @@ function init_context()
225225
CTX.lib_path === missing && error("""
226226
Could not find Python library for Python executable $(repr(CTX.exe_path)).
227227
228-
If you know where the library is, set environment variable 'JULIA_PYTHONCALL_LIB' to its path.
228+
If you know where the library is, set the 'lib' preference or 'JULIA_PYTHONCALL_LIB' environment variable to its path.
229229
""")
230230
end
231231

src/Compat/serialization.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# We use pickle to serialise Python objects to bytes.
44

5-
_pickle_module() = pyimport(get(ENV, "JULIA_PYTHONCALL_PICKLE", "pickle"))
5+
_pickle_module() = pyimport(Utils.getpref_pickle())
66

77
function serialize_py(s, x::Py)
88
if pyisnull(x)

src/Utils/Utils.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
module Utils
22

3+
using Preferences: @load_preference
4+
5+
function getpref(::Type{T}, prefname, envname, default = nothing) where {T}
6+
ans = @load_preference(prefname, nothing)
7+
ans === nothing || return checkpref(T, ans)::T
8+
ans = get(ENV, envname, "")
9+
isempty(ans) || return checkpref(T, ans)::T
10+
return default
11+
end
12+
13+
checkpref(::Type{String}, x) = string(x)
14+
checkpref(::Type{String}, x::AbstractString) = convert(String, x)
15+
16+
# Specific preference functions
17+
getpref_exe() = getpref(String, "exe", "JULIA_PYTHONCALL_EXE", "")
18+
getpref_lib() = getpref(String, "lib", "JULIA_PYTHONCALL_LIB", nothing)
19+
getpref_pickle() = getpref(String, "pickle", "JULIA_PYTHONCALL_PICKLE", "pickle")
20+
321
function explode_union(T)
422
@nospecialize T
523

0 commit comments

Comments
 (0)