Skip to content

Commit bcd976a

Browse files
author
Christopher Rowley
committed
Merge remote-tracking branch 'origin/main' into v1
2 parents bf1ff38 + 79e8a44 commit bcd976a

16 files changed

Lines changed: 194 additions & 96 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
- name: Run tests
6565
uses: julia-actions/julia-runtest@v1
6666
env:
67-
JULIA_DEBUG: PythonCall
67+
JULIA_DEBUG: ${{ runner.debug == '1' && 'PythonCall' || '' }}
6868
JULIA_NUM_THREADS: '2'
6969
PYTHON: ${{ steps.setup-python.outputs.python-path }}
7070
JULIA_PYTHONCALL_EXE: ${{ case(matrix.pythonexe == 'python', steps.setup-python.outputs.python-path, matrix.pythonexe) }}

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@
3232
* `pyjltype(x)` removed.
3333
* New functions: `pyjlarray`, `pyjldict`, `pyjlset`.
3434

35+
## Unreleased
36+
* Add option `lib` to JuliaCall. Setting this will skip the discovery subprocess.
37+
* Add support for using a system image in `juliacall` that has `PythonCall` baked in.
38+
* Add method `pynext(x, d)` to return a default value `d` if there are no more elements.
39+
* Bug fixes.
40+
41+
## 0.9.34 (2026-05-18)
42+
* Bug fixes.
43+
3544
## 0.9.33 (2026-05-18)
3645
* Add configuration via Preferences in addition to environment variables (e.g. `exe`
3746
rather than `JULIA_PYTHONCALL_EXE`.)

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PythonCall"
22
uuid = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
33
authors = ["Christopher Doris <github.com/cjdoris>"]
4-
version = "0.9.33"
4+
version = "0.9.34"
55

66
[deps]
77
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"

docs/src/juliacall.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ be configured in two ways:
142142
| `-X juliacall-heap-size-hint=<N>` | `PYTHON_JULIACALL_HEAP_SIZE_HINT=<N>` | Hint for initial heap size in bytes. |
143143
| `-X juliacall-exe=<file>` | `PYTHON_JULIACALL_EXE=<file>` | Path to Julia binary to use (overrides JuliaPkg). |
144144
| `-X juliacall-project=<dir>` | `PYTHON_JULIACALL_PROJECT=<dir>` | Path to the Julia project to use (overrides JuliaPkg). |
145+
| `-X juliacall-lib=<file>` | `PYTHON_JULIACALL_LIB=<file>` | Path to libjulia. Set to skip the relatively slow discovery process. |
145146
| `-X juliacall-trace-compile=<stderr\|name>` | `PYTHON_JULIACALL_TRACE_COMPILE=<stderr\|name>` | Print precompile statements. |
146147
| `-X juliacall-trace-compile-timing` | `PYTHON_JULIACALL_TRACE_COMPILE_TIMING=<yes\|no>` | Include timings with precompile statements. |
147148

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "juliacall"
7-
version = "0.9.33"
7+
version = "0.9.34"
88
description = "Julia and Python in seamless harmony"
99
readme = { file = "README.md", content-type = "text/markdown" }
1010
classifiers = [
@@ -13,7 +13,7 @@ classifiers = [
1313
"Operating System :: OS Independent",
1414
]
1515
requires-python = ">=3.10, <4"
16-
dependencies = ["juliapkg >=0.1.21, <0.2"]
16+
dependencies = ["juliapkg >=0.1.24, <0.2"]
1717

1818
[dependency-groups]
1919
dev = [

pysrc/juliacall/__init__.py

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This module gets modified by PythonCall when it is loaded, e.g. to include Core, Base
22
# and Main modules.
33

4-
__version__ = '0.9.33'
4+
__version__ = '0.9.34'
55

66
_newmodule = None
77

@@ -170,39 +170,47 @@ def args_from_config(config):
170170
CONFIG['opt_handle_signals'] = choice('handle_signals', ['yes', 'no'])[0]
171171
CONFIG['opt_startup_file'] = choice('startup_file', ['yes', 'no'])[0]
172172
CONFIG['opt_heap_size_hint'] = option('heap_size_hint')[0]
173-
CONFIG['project'] = path_option('project', check_exists=True)[0]
174-
CONFIG['exepath'] = executable_option('exe')[0]
173+
CONFIG['project'] = project = path_option('project', check_exists=True)[0]
174+
CONFIG['libpath'] = libpath = path_option('lib', check_exists=True)[0]
175+
CONFIG['exepath'] = exepath = executable_option('exe')[0]
175176

176177
# Stop if we already initialised
177178
if CONFIG['inited']:
178179
return
179180

180-
have_exepath = CONFIG['exepath'] is not None
181-
have_project = CONFIG['project'] is not None
182-
if have_exepath and have_project:
181+
if (exepath is None) and (bindir is not None):
182+
# if bindir is set then set exepath={bindir}/julia
183+
CONFIG['exepath'] = exepath = os.path.join(bindir, 'julia.exe' if os.name == 'nt' else 'julia')
184+
if (exepath is not None) and (project is not None):
183185
pass
184-
elif (not have_exepath) and (not have_project):
186+
elif (exepath is None) and (project is None):
185187
# we don't import this at the top level because it is not required when
186188
# juliacall is loaded by PythonCall and won't be available, or if both
187189
# `exepath` and `project` are set by the user.
188190
import juliapkg
189191

190192
# Find the Julia executable and project
191-
CONFIG['exepath'] = juliapkg.executable()
192-
CONFIG['project'] = juliapkg.project()
193+
CONFIG['exepath'] = exepath = juliapkg.executable()
194+
CONFIG['project'] = project = juliapkg.project()
195+
if libpath is None:
196+
CONFIG['libpath'] = libpath = juliapkg.libjulia()
193197
else:
194198
raise Exception("Both PYTHON_JULIACALL_PROJECT and PYTHON_JULIACALL_EXE must be set together, not only one of them.")
195-
196-
exepath = CONFIG['exepath']
197-
project = CONFIG['project']
198-
199-
# Find the Julia library
200-
cmd = [exepath, '--project='+project, '--startup-file=no', '-O0', '--compile=min',
201-
'-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")), "\\0", Sys.BINDIR)']
202-
libpath, default_bindir = subprocess.run(cmd, check=True, capture_output=True, encoding='utf8').stdout.split('\0')
199+
if (libpath is not None) and (exepath is None):
200+
raise Exception("PYTHON_JULIACALL_EXE is required if PYTHON_JULIACALL_LIB is set.")
201+
202+
# Find the Julia library, if not specified.
203+
if libpath is None:
204+
cmd = [exepath, '--project='+project, '--startup-file=no', '-O0', '--compile=min',
205+
'-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")), "\\0", Sys.BINDIR)']
206+
libpath, found_bindir = subprocess.run(cmd, check=True, capture_output=True, encoding='utf8').stdout.split('\0')
207+
CONFIG['libpath'] = libpath
208+
if bindir is None:
209+
CONFIG['bindir'] = bindir = found_bindir
210+
if bindir is None:
211+
bindir = os.path.dirname(exepath)
203212
assert os.path.exists(libpath)
204-
assert os.path.exists(default_bindir)
205-
CONFIG['libpath'] = libpath
213+
assert os.path.exists(bindir)
206214

207215
# Add the Julia library directory to the PATH on Windows so Julia's system libraries can
208216
# be found. They are normally found because they are in the same directory as julia.exe,
@@ -225,6 +233,11 @@ def args_from_config(config):
225233
jl_parse_opts(c.pointer(argc), c.pointer(argv))
226234
assert argc.value == 0
227235

236+
# override some environment variables
237+
# we do this here because PythonCall is initialised during jl_init if it is in a sysimg
238+
os.environ['JULIA_PYTHONCALL_EXECUTABLE'] = sys.executable or ''
239+
os.environ['__JULIA_PYTHONCALL_EMBEDDED_LIBPTR__'] = hex(c.pythonapi._handle)
240+
228241
# initialise julia
229242
try:
230243
jl_init = lib.jl_init_with_image__threading
@@ -233,7 +246,7 @@ def args_from_config(config):
233246
jl_init.argtypes = [c.c_char_p, c.c_char_p]
234247
jl_init.restype = None
235248
jl_init(
236-
(default_bindir if bindir is None else bindir).encode('utf8'),
249+
None if bindir is None else bindir.encode('utf8'),
237250
None if sysimg is None else sysimg.encode('utf8'),
238251
)
239252

@@ -254,23 +267,22 @@ def jlstr(x):
254267
return 'raw"' + x + '"'
255268
script = '''
256269
try
257-
Base.require(Main, :CompilerSupportLibraries_jll)
258-
global __PythonCall_libptr = Ptr{{Cvoid}}(UInt({}))
259-
ENV["JULIA_PYTHONCALL_EXE"] = {}
270+
import CompilerSupportLibraries_jll as _
260271
using PythonCall
261272
catch err
262273
print(stderr, "ERROR: ")
263274
showerror(stderr, err, catch_backtrace())
264275
flush(stderr)
265276
rethrow()
266277
end
267-
'''.format(
268-
hex(c.pythonapi._handle),
269-
jlstr(sys.executable or ''),
270-
)
278+
'''
271279
res = jl_eval(script.encode('utf8'))
272280
if res is None:
273281
raise Exception('PythonCall.jl did not start properly')
282+
283+
# unset this env var so any other julia processes started do not think they are
284+
# embedded in python
285+
os.environ.pop('__JULIA_PYTHONCALL_EMBEDDED_LIBPTR__', None)
274286

275287
CONFIG['inited'] = True
276288

pysrc/juliacall/juliapkg-dev.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"packages": {
44
"PythonCall": {
55
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
6-
"version": "=0.9.33",
6+
"version": "=0.9.34",
77
"path": "../..",
88
"dev": true
99
},

pysrc/juliacall/juliapkg.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
"packages": {
44
"PythonCall": {
55
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
6-
"version": "=0.9.33",
7-
"path": "../..",
8-
"dev": true
6+
"version": "=0.9.34"
97
},
108
"OpenSSL_jll": {
119
"uuid": "458c3c95-2e84-50aa-8efc-19380b2a3a95",

src/API/API.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"The version of PythonCall."
2-
const VERSION = v"0.9.33"
2+
const VERSION = v"0.9.34"
33

44
include("types.jl")
55
include("functions.jl")

src/C/context.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,14 @@ on_main_thread
105105

106106
function init_context()
107107

108-
CTX.is_embedded = hasproperty(Base.Main, :__PythonCall_libptr)
108+
CTX.is_embedded = haskey(ENV, "__JULIA_PYTHONCALL_EMBEDDED_LIBPTR__")
109109

110110
if CTX.is_embedded
111111
# In this case, getting a handle to libpython is easy
112-
CTX.lib_ptr = Base.Main.__PythonCall_libptr::Ptr{Cvoid}
112+
CTX.lib_ptr = Ptr{Cvoid}(parse(UInt, ENV["__JULIA_PYTHONCALL_EMBEDDED_LIBPTR__"]))
113+
# Delete the env var so subprocesses don't think they are embedded
114+
delete!(ENV, "__JULIA_PYTHONCALL_EMBEDDED_LIBPTR__")
115+
# Initialise pointers from libpython
113116
init_pointers()
114117
# Check Python is initialized
115118
Py_IsInitialized() == 0 && error("Python is not already initialized.")

0 commit comments

Comments
 (0)