From f05d9a0398a901e66155f7c20fd690ea17a7f70d Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Sun, 2 Nov 2025 11:37:53 +0000 Subject: [PATCH 1/3] Add numpy-backed ArrayValue tests --- CondaPkg.toml | 1 + src/JlWrap/array.jl | 12 +++++------- test/JlWrap.jl | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CondaPkg.toml b/CondaPkg.toml index fb185c61..4e0c0e7a 100644 --- a/CondaPkg.toml +++ b/CondaPkg.toml @@ -14,5 +14,6 @@ version = ">=3.10,<4" [dev.deps] matplotlib = "" +numpy = "" pyside6 = "" python = "<3.14" diff --git a/src/JlWrap/array.jl b/src/JlWrap/array.jl index f063c191..299aedcc 100644 --- a/src/JlWrap/array.jl +++ b/src/JlWrap/array.jl @@ -355,22 +355,20 @@ class ArrayValue(AnyValue): @property def __array_interface__(self): return self._jl_callmethod($(pyjl_methodnum(pyjlarray_array_interface))) - def __array__(self, dtype=None): + def __array__(self, dtype=None, copy=None): + import numpy # convert to an array-like object arr = self if not (hasattr(arr, "__array_interface__") or hasattr(arr, "__array_struct__")): + if copy is False: + raise ValueError("copy=False is not supported when collecting ArrayValue data") # the first attempt collects into an Array arr = self._jl_callmethod($(pyjl_methodnum(pyjlarray_array__array))) if not (hasattr(arr, "__array_interface__") or hasattr(arr, "__array_struct__")): # the second attempt collects into a PyObjectArray arr = self._jl_callmethod($(pyjl_methodnum(pyjlarray_array__pyobjectarray))) # convert to a numpy array if numpy is available - try: - import numpy - arr = numpy.array(arr, dtype=dtype) - except ImportError: - pass - return arr + return numpy.array(arr, dtype=dtype, copy=copy) def to_numpy(self, dtype=None, copy=True, order="K"): import numpy return numpy.array(self, dtype=dtype, copy=copy, order=order) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 56bf0e8e..fd84796b 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -313,6 +313,38 @@ end @test pyjlvalue(x) == [0 2; 3 4] @test pyjlvalue(y) == [1 2; 3 4] end + @testset "__array__" begin + np = pyimport("numpy") + + numeric = pyjl(Float64[1, 2, 3]) + numeric_array = numeric.__array__() + @test pyisinstance(numeric_array, np.ndarray) + @test pyconvert(Vector{Float64}, numeric_array) == [1.0, 2.0, 3.0] + + numeric_no_copy = numeric.__array__(copy=false) + numeric_data = pyjlvalue(numeric) + numeric_data[1] = 42.0 + @test pyconvert(Vector{Float64}, numeric_no_copy) == [42.0, 2.0, 3.0] + + string_array = pyjl(["a", "b"]) + string_result = string_array.__array__() + @test pyisinstance(string_result, np.ndarray) + @test pyconvert(Vector{String}, pybuiltins.list(string_result)) == ["a", "b"] + + err = try + string_array.__array__(copy=false) + nothing + catch err + err + end + @test err !== nothing + @test err isa PythonCall.PyException + @test pyis(err._t, pybuiltins.ValueError) + @test occursin( + "copy=False is not supported when collecting ArrayValue data", + sprint(showerror, err), + ) + end @testset "array_interface" begin x = pyjl(Float32[1 2 3; 4 5 6]).__array_interface__ @test pyisinstance(x, pybuiltins.dict) From 421dc5e34ba22b32d882b9d12e920f97d2fa62f0 Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Sun, 2 Nov 2025 12:37:37 +0000 Subject: [PATCH 2/3] Guard ArrayValue numpy tests on devdeps --- test/JlWrap.jl | 55 +++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index fd84796b..0888ee53 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -314,36 +314,41 @@ end @test pyjlvalue(y) == [1 2; 3 4] end @testset "__array__" begin - np = pyimport("numpy") + devdeps = PythonCall.C.CTX.which == :CondaPkg + if devdeps + np = pyimport("numpy") - numeric = pyjl(Float64[1, 2, 3]) - numeric_array = numeric.__array__() - @test pyisinstance(numeric_array, np.ndarray) - @test pyconvert(Vector{Float64}, numeric_array) == [1.0, 2.0, 3.0] + numeric = pyjl(Float64[1, 2, 3]) + numeric_array = numeric.__array__() + @test pyisinstance(numeric_array, np.ndarray) + @test pyconvert(Vector{Float64}, numeric_array) == [1.0, 2.0, 3.0] - numeric_no_copy = numeric.__array__(copy=false) - numeric_data = pyjlvalue(numeric) - numeric_data[1] = 42.0 - @test pyconvert(Vector{Float64}, numeric_no_copy) == [42.0, 2.0, 3.0] + numeric_no_copy = numeric.__array__(copy=false) + numeric_data = pyjlvalue(numeric) + numeric_data[1] = 42.0 + @test pyconvert(Vector{Float64}, numeric_no_copy) == [42.0, 2.0, 3.0] - string_array = pyjl(["a", "b"]) - string_result = string_array.__array__() - @test pyisinstance(string_result, np.ndarray) - @test pyconvert(Vector{String}, pybuiltins.list(string_result)) == ["a", "b"] + string_array = pyjl(["a", "b"]) + string_result = string_array.__array__() + @test pyisinstance(string_result, np.ndarray) + @test pyconvert(Vector{String}, pybuiltins.list(string_result)) == ["a", "b"] - err = try - string_array.__array__(copy=false) - nothing - catch err - err + err = try + string_array.__array__(copy=false) + nothing + catch err + err + end + @test err !== nothing + @test err isa PythonCall.PyException + @test pyis(err._t, pybuiltins.ValueError) + @test occursin( + "copy=False is not supported when collecting ArrayValue data", + sprint(showerror, err), + ) + else + @test_skip devdeps end - @test err !== nothing - @test err isa PythonCall.PyException - @test pyis(err._t, pybuiltins.ValueError) - @test occursin( - "copy=False is not supported when collecting ArrayValue data", - sprint(showerror, err), - ) end @testset "array_interface" begin x = pyjl(Float32[1 2 3; 4 5 6]).__array_interface__ From 1afe7739327d5ebc96d9620ab75b697a9bb29ebf Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Sun, 2 Nov 2025 12:37:41 +0000 Subject: [PATCH 3/3] Use Setup module for ArrayValue numpy tests --- test/JlWrap.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/JlWrap.jl b/test/JlWrap.jl index 0888ee53..52d93642 100644 --- a/test/JlWrap.jl +++ b/test/JlWrap.jl @@ -215,7 +215,7 @@ end end -@testitem "array" begin +@testitem "array" setup=[Setup] begin @testset "type" begin @test pyis(pytype(pyjl(fill(nothing))), PythonCall.pyjlarraytype) @test pyis(pytype(pyjl([1 2; 3 4])), PythonCall.pyjlarraytype) @@ -314,8 +314,7 @@ end @test pyjlvalue(y) == [1 2; 3 4] end @testset "__array__" begin - devdeps = PythonCall.C.CTX.which == :CondaPkg - if devdeps + if Setup.devdeps np = pyimport("numpy") numeric = pyjl(Float64[1, 2, 3]) @@ -347,7 +346,7 @@ end sprint(showerror, err), ) else - @test_skip devdeps + @test_skip Setup.devdeps end end @testset "array_interface" begin