Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
matrix:
arch: [x64] # x86 unsupported by MicroMamba
os: [ubuntu-latest, windows-latest, macos-latest]
jlversion: ['1','1.9']
jlversion: ['1','1.10']
pythonexe: ['@CondaPkg']
include:
- arch: x64
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
pyversion: ["3", "3.9"]
pyversion: ["3", "3.10"]
juliaexe: ["@JuliaPkg"]
include:
- os: ubuntu-latest
Expand All @@ -84,17 +84,25 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.pyversion }}

- name: Check Python OpenSSL version (see setup_julia)
shell: python
run: |
import ssl
assert ssl.OPENSSL_VERSION_INFO < (3, 5)

- name: Set up uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.pyversion }}

- name: Set up Julia
id: setup_julia
uses: julia-actions/setup-julia@v2
with:
version: '1'
# Python in the GitHub runners ships with OpenSSL 3.0. Julia 1.12 requires
# OpenSSL 3.5. Therefore juliapkg requires Julia 1.11 or lower.
version: '1.11'

- name: Set up test Julia project
if: ${{ matrix.juliaexe == 'julia' }}
Expand Down
13 changes: 9 additions & 4 deletions CondaPkg.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[deps.libstdcxx]

[deps.openssl]
version = "<=julia"

[deps.libstdcxx-ng]
[deps.libstdcxx]
version = "<=julia"

[deps.openssl]
[deps.libstdcxx-ng]
version = "<=julia"

[deps.python]
build = "**cpython**"
version = ">=3.9,<4"
version = ">=3.10,<4"

[dev.deps]
matplotlib = ""
pyside6 = ""
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
[compat]
Aqua = "0 - 999"
CategoricalArrays = "0.10, 1"
CondaPkg = "0.2.30"
CondaPkg = "0.2.33"
Dates = "1"
Libdl = "1"
MacroTools = "0.5"
Expand All @@ -29,7 +29,7 @@ Tables = "1"
Test = "1"
TestItemRunner = "0 - 999"
UnsafePointers = "1"
julia = "1.9"
julia = "1.10"

[extensions]
PyCallExt = "PyCall"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Bringing [**Python®**](https://www.python.org/) and [**Julia**](https://juliala
- Fast non-copying conversion of numeric arrays in either direction: modify Python arrays (e.g. `bytes`, `array.array`, `numpy.ndarray`) from Julia or Julia arrays from Python.
- Helpful wrappers: interpret Python sequences, dictionaries, arrays, dataframes and IO streams as their Julia counterparts, and vice versa.
- Beautiful stack-traces.
- Supports modern systems: tested on Windows, MacOS and Linux, 64-bit, Julia 1.9 upwards and Python 3.9 upwards.
- Supports modern systems: tested on Windows, MacOS and Linux, 64-bit, Julia 1.10 upwards and Python 3.10 upwards.

⭐ If you like this, a GitHub star would be lovely thank you. ⭐

Expand Down
8 changes: 8 additions & 0 deletions docs/src/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,11 @@ using PythonCall

np = pyimport("numpy")
```

## What versions of Python and Julia do you support?

Each release of PythonCall and JuliaCall will support and require:
- Any currently supported version of Python, [see here](https://devguide.python.org/versions/). Currently 3.10+.
- The current Julia LTS version and newer, [see here](https://julialang.org/downloads/#long_term_support_release). Currently 1.10+.

Only the latest patch release within each minor version is supported.
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ Bringing [**Python®**](https://www.python.org/) and [**Julia**](https://juliala
- Fast non-copying conversion of numeric arrays in either direction: modify Python arrays (e.g. `bytes`, `array.array`, `numpy.ndarray`) from Julia or Julia arrays from Python.
- Helpful wrappers: interpret Python sequences, dictionaries, arrays, dataframes and IO streams as their Julia counterparts, and vice versa.
- Beautiful stack-traces.
- Works anywhere: tested on Windows, MacOS and Linux, 32- and 64-bit, Julia Julia 1.9 upwards and Python 3.9 upwards.
- Works anywhere: tested on Windows, MacOS and Linux, 32- and 64-bit, Julia Julia 1.10 upwards and Python 3.10 upwards.
4 changes: 2 additions & 2 deletions docs/src/juliacall-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ This wraps any Julia `AbstractVector` value. It is a subclass of `juliacall.Arra

`````@customdoc
juliacall.DictValue - Class
This wraps any Julia `AbstractDict` value. It is a subclass of `collections.abc.Mapping` and
This wraps any Julia `AbstractDict` value. It is a subclass of `collections.abc.MutableMapping` and
behaves similar to a Python `dict`.
`````

`````@customdoc
juliacall.SetValue - Class
This wraps any Julia `AbstractSet` value. It is a subclass of `collections.abc.Set` and
This wraps any Julia `AbstractSet` value. It is a subclass of `collections.abc.MutableSet` and
behaves similar to a Python `set`.
`````

Expand Down
9 changes: 9 additions & 0 deletions docs/src/juliacall.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ It's as simple as
pip install juliacall
```

If you prefer Conda, there is a community effort to also release this on conda-forge:
```bash
conda install conda-forge::pyjuliapkg
```

Developers may wish to clone the repo (https://github.com/JuliaPy/PythonCall.jl) directly
and pip install the module in editable mode. You should add `"dev":true, "path":"../.."` to
`pysrc/juliacall/juliapkg.json` to ensure you use the development version of PythonCall
Expand Down Expand Up @@ -140,6 +145,10 @@ be configured in two ways:

## [Multi-threading](@id py-multi-threading)

!!! warning

Multi-threading support is experimental and can change without notice.

From v0.9.22, JuliaCall supports multi-threading in Julia and/or Python, with some
caveats.

Expand Down
52 changes: 51 additions & 1 deletion docs/src/pythoncall.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This package is in the general registry, so to install just type `]` in the Juli
pkg> add PythonCall
```

## Getting started
## [Getting started](@id py_getting_started)

Import the module with:

Expand Down Expand Up @@ -91,6 +91,52 @@ Python: ValueError('some error')
With the functions introduced so far, you have access to the vast majority of Python's
functionality.

## Executing Python scripts

A common use case is calling multiple blocks of Python code from Julia interactively. This can be accomplished in PythonCall via the [@pyexec](@ref) macro. For example, the sentence parsing application in the [Getting started](@ref py_getting_started) section could be rewritten as:

```julia-repl
julia> @pyexec """
global re
import re

def my_sentence(s):
words = re.findall("[a-zA-Z]+", s)
sentence = " ".join(words)
return sentence
""" => my_sentence
Python: <function my_sentence at 0x7d83bb1b01a0>

julia> sentence = my_sentence("PythonCall.jl is very useful!")
Python: 'PythonCall jl is very useful'
```

Note the use of the `global` keyword to make the `re` package accessible in global scope, and the `=> my_sentence` syntax to create a Julia function named `my_sentence` that calls to the Python function of the same name. This syntax also supports calling to multiple functions and passing data back-and-forth:

```julia-repl
julia> @pyexec (num=10) => """
def add(a, b):
return a + b

def subtract(a, b):
return a - b

plusone = num + 1
""" => (add, subtract, plusone::Float64)
(add = <py function add at 0x721b001a7cc0>, subtract = <py function subtract at 0x721b001a7d70>, plusone = 11.0)

julia> add(4, 3)
Python: 7

julia> subtract(4, 3)
Python: 1

julia> plusone
11.0
```

Here we demonstrate passing a named variable, `num`, via the use of the `=>` syntax again, and returning named output, with the last element, `plusone`, being cast to a Julia object via the `::` syntax. See [@pyexec](@ref), [@pyeval](@ref), and their functional forms [pyexec](@ref) and [pyeval](@ref), for more.

## Conversion between Julia and Python

A Julia object can be converted to a Python one either explicitly (such as `Py(x)`) or
Expand Down Expand Up @@ -369,6 +415,10 @@ See [Installing Python packages](@ref python-deps).

## [Multi-threading](@id jl-multi-threading)

!!! warning

Multi-threading support is experimental and can change without notice.

From v0.9.22, PythonCall supports multi-threading in Julia and/or Python, with some
caveats.

Expand Down
8 changes: 8 additions & 0 deletions docs/src/releasenotes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

## Unreleased
* Minimum supported Python version is now 3.10.
* Minimum supported Julia version is now 1.10.
* Showing `Py` now respects the `compact` option - output is limited to a single line of
at most the display width.
* Support policy now documented in the FAQ.
* Bug fixes.

## 0.9.28 (2025-09-17)
* Added `NumpyDates`: NumPy-compatible DateTime64/TimeDelta64 types and units.
* Added `pyconvert` rules for NumpyDates types.
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.9, <4"
dependencies = ["juliapkg >=0.1.17, <0.2"]
requires-python = ">=3.10, <4"
dependencies = ["juliapkg >=0.1.21, <0.2"]

[dependency-groups]
dev = [
Expand Down
2 changes: 1 addition & 1 deletion pysrc/juliacall/juliapkg-dev.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"julia": "~1.9, ^1.10.3",
"julia": "^1.10.3",
"packages": {
"PythonCall": {
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
Expand Down
2 changes: 1 addition & 1 deletion pysrc/juliacall/juliapkg.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"julia": "~1.9, ^1.10.3",
"julia": "^1.10.3",
"packages": {
"PythonCall": {
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
Expand Down
79 changes: 77 additions & 2 deletions src/C/context.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,79 @@ function _atpyexit()
return
end


function setup_onfixedthread()
channel_input = Channel(1)
channel_output = Channel(1)
islaunched = Ref(false) # use Ref to avoid closure boxing of variable
function launch_worker(tid)
islaunched[] && error("Cannot launch more than once: call setup_onfixedthread again if need be.")
islaunched[] = true
worker_task = Task() do
while true
f = take!(channel_input)
ret = try
Some(invokelatest(f))
# invokelatest is necessary for development and interactive use.
# Otherwise, only a method f defined in a world prior to the call of
# launch_worker would work.
catch e
e, catch_backtrace()
end
put!(channel_output, ret)
end
end
# code adapted from set_task_tid! in StableTasks.jl, itself taken from Dagger.jl
worker_task.sticky = true
for _ in 1:100
# try to fix the task id to tid, retrying up to 100 times
ret = ccall(:jl_set_task_tid, Cint, (Any, Cint), worker_task, tid-1)
if ret == 1
break # success
elseif ret == 0
yield()
else
error("Unexpected retcode from jl_set_task_tid: $ret")
end
end
if Threads.threadid(worker_task) != tid
error("Failed setting the thread ID to $tid.")
end
schedule(worker_task)
end
function onfixedthread(f)
put!(channel_input, f)
ret = take!(channel_output)
if ret isa Tuple
e, backtrace = ret
printstyled(stderr, "ERROR: "; color=:red, bold=true)
showerror(stderr, e)
Base.show_backtrace(stderr, backtrace)
println(stderr)
throw(e) # the stacktrace of the actual error is printed above
else
something(ret)
end
end
launch_worker, onfixedthread
end

# launch_on_main_thread is used in init_context(), after which on_main_thread becomes usable
const launch_on_main_thread, on_main_thread = setup_onfixedthread()

"""
on_main_thread(f)

Execute `f()` on the main thread.

!!! warning
The value returned by `on_main_thread(f)` cannot be type-inferred by the compiler:
if necessary, use explicit type annotations such as `on_main_thread(f)::T`, where `T` is
the expected return type.
"""
on_main_thread


function init_context()

CTX.is_embedded = hasproperty(Base.Main, :__PythonCall_libptr)
Expand Down Expand Up @@ -236,10 +309,12 @@ function init_context()
error("Cannot parse version from version string: $(repr(verstr))")
end
CTX.version = VersionNumber(vermatch.match)
v"3.9" ≤ CTX.version < v"4" || error(
"Only Python 3.9+ is supported, this is Python $(CTX.version) at $(CTX.exe_path===missing ? "unknown location" : CTX.exe_path).",
v"3.10" ≤ CTX.version < v"4" || error(
"Only Python 3.10+ is supported, this is Python $(CTX.version) at $(CTX.exe_path===missing ? "unknown location" : CTX.exe_path).",
)

launch_on_main_thread(Threads.threadid()) # makes on_main_thread usable

@debug "Initialized PythonCall.jl" CTX.is_embedded CTX.is_initialized CTX.exe_path CTX.lib_path CTX.lib_ptr CTX.pyprogname CTX.pyhome CTX.version

return
Expand Down
2 changes: 1 addition & 1 deletion src/Compat/gui.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ function init_gui()

# add a hook to automatically call fix_qt_plugin_path()
fixqthook =
Py(() -> (Core.CONFIG.auto_fix_qt_plugin_path && fix_qt_plugin_path(); nothing))
Py(() -> (PythonCall.CONFIG.auto_fix_qt_plugin_path && fix_qt_plugin_path(); nothing))
pymodulehooks.add_hook("PyQt4", fixqthook)
pymodulehooks.add_hook("PyQt5", fixqthook)
pymodulehooks.add_hook("PySide", fixqthook)
Expand Down
Loading
Loading