Skip to content

Commit 3aa1602

Browse files
authored
add Preferences.jl-based configuration (#742)
* add Preferences.jl-based configuration * nitpicks * docs nitpicks --------- Co-authored-by: Christopher Rowley <github.com/cjdoris>
1 parent e364646 commit 3aa1602

7 files changed

Lines changed: 96 additions & 24 deletions

File tree

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

285320
```julia
286321
ENV["JULIA_CONDAPKG_BACKEND"] = "Null"
287322
ENV["JULIA_PYTHONCALL_EXE"] = "/path/to/python" # optional
288-
ENV["JULIA_PYTHONCALL_EXE"] = "@PyCall" # optional
289-
ENV["JULIA_PYTHONCALL_EXE"] = "@venv" # optional
323+
```
324+
325+
Or using preferences:
326+
```
327+
pkg> preference add CondaPkg backend=Null
328+
pkg> preference add PythonCall exe=/path/to/python # optional
290329
```
291330

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

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.
335+
If `python` is not in your `PATH`, you will also need to set `exe` (or `JULIA_PYTHONCALL_EXE`)
336+
to its path. Relative paths are resolved relative to the current active project.
298337

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).
338+
If you also use PyCall, you can set `exe=@PyCall` to use the same Python interpreter.
339+
[See here](@ref faq-pycall).
301340

302341
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.
342+
can set `exe=@venv` to use it.
304343

305-
#### If you already have a Conda environment
344+
### If you already have a Conda environment
306345

307346
```julia
308347
ENV["JULIA_CONDAPKG_BACKEND"] = "Current"
309348
ENV["JULIA_CONDAPKG_EXE"] = "/path/to/conda" # optional
310349
```
311350

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

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

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

322-
#### If you already have Conda, Mamba or MicroMamba
367+
### If you already have Conda, Mamba, MicroMamba or Pixi
323368

324369
```julia
325-
ENV["JULIA_CONDAPKG_BACKEND"] = "System"
370+
ENV["JULIA_CONDAPKG_BACKEND"] = "System" # or "SystemPixi" for Pixi
326371
ENV["JULIA_CONDAPKG_EXE"] = "/path/to/conda" # optional
327372
```
328373

329-
The System backend to CondaPkg will use your preinstalled Conda implementation instead of
330-
downloading one.
374+
Or using preferences:
375+
```
376+
pkg> preference add CondaPkg backend=System
377+
pkg> preference add CondaPkg exe=/path/to/conda # optional
378+
```
379+
380+
The System (or SystemPixi) backend to CondaPkg will use your preinstalled Conda (or
381+
Pixi) implementation instead of downloading one.
331382

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

335-
If `conda`, `mamba` or `micromamba` is not in your `PATH` you will also need to set
336-
`JULIA_CONDAPKG_EXE` to its path.
386+
If `conda`, `mamba`, `micromamba` or `pixi` is not in your `PATH` you will also need to
387+
set `JULIA_CONDAPKG_EXE` (or the CondaPkg `exe` preference) to its path.
337388

338389
## [Installing Python packages](@id python-deps)
339390

src/C/C.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ This module provides a direct interface to the Python C API.
55
"""
66
module C
77

8+
using ..Utils
9+
810
using Base: @kwdef
911
using UnsafePointers: UnsafePtr
1012
using CondaPkg: CondaPkg
@@ -15,7 +17,6 @@ using Libdl:
1517
import ..PythonCall:
1618
python_executable_path, python_library_path, python_library_handle, python_version
1719

18-
1920
include("consts.jl")
2021
include("pointers.jl")
2122
include("extras.jl")

src/C/context.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,15 @@ function init_context()
115115
Py_IsInitialized() == 0 && error("Python is not already initialized.")
116116
CTX.is_initialized = true
117117
CTX.which = :embedded
118-
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
118+
exe_path = Utils.getpref_exe()
119119
if exe_path != ""
120120
CTX.exe_path = exe_path
121121
# this ensures PyCall uses the same Python interpreter
122122
get!(ENV, "PYTHON", exe_path)
123123
end
124124
else
125125
# Find Python executable
126-
exe_path = get(ENV, "JULIA_PYTHONCALL_EXE", "")
126+
exe_path = Utils.getpref_exe()
127127
if exe_path == "" || exe_path == "@CondaPkg"
128128
if CondaPkg.backend() == :Null
129129
exe_path = Sys.which("python")
@@ -158,7 +158,7 @@ function init_context()
158158
exe_path = abspath(exe_path, "bin", "python")::String
159159
end
160160
elseif startswith(exe_path, "@")
161-
error("invalid JULIA_PYTHONCALL_EXE=$exe_path")
161+
error("invalid exe: $exe_path")
162162
else
163163
# Otherwise we use the Python specified
164164
CTX.which = :unknown
@@ -199,7 +199,7 @@ function init_context()
199199
# Find and open Python library
200200
lib_path = something(
201201
CTX.lib_path === missing ? nothing : CTX.lib_path,
202-
get(ENV, "JULIA_PYTHONCALL_LIB", nothing),
202+
Utils.getpref_lib(),
203203
Some(nothing),
204204
)
205205
if lib_path !== nothing
@@ -226,7 +226,7 @@ function init_context()
226226
CTX.lib_path === missing && error("""
227227
Could not find Python library for Python executable $(repr(CTX.exe_path)).
228228
229-
If you know where the library is, set environment variable 'JULIA_PYTHONCALL_LIB' to its path.
229+
If you know where the library is, set the 'lib' preference or 'JULIA_PYTHONCALL_LIB' environment variable to its path.
230230
""")
231231
end
232232

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) = error("invalid preference of type $(type(x)), expecting a string")
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)