Skip to content

Commit 8d65246

Browse files
authored
Switch from PyCall to PythonCall for testing (#176)
* Switch from PyCall to PythonCall * enable other tests * show julia version info * reduce changes
1 parent 6c8f5c4 commit 8d65246

File tree

7 files changed

+123
-141
lines changed

7 files changed

+123
-141
lines changed

.github/workflows/CI.yml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,15 @@ jobs:
2020
- macOS-latest
2121
- windows-latest
2222
arch:
23-
- x64
23+
- 'default'
2424
steps:
2525
- uses: actions/checkout@v4
2626
- uses: julia-actions/setup-julia@v2
2727
with:
2828
version: ${{ matrix.version }}
2929
arch: ${{ matrix.arch }}
30-
- uses: actions/cache@v4
31-
env:
32-
cache-name: cache-artifacts
33-
with:
34-
path: ~/.julia/artifacts
35-
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
36-
restore-keys: |
37-
${{ runner.os }}-test-${{ env.cache-name }}-
38-
${{ runner.os }}-test-
39-
${{ runner.os }}-
30+
show-versioninfo: true
31+
- uses: julia-actions/cache@v2
4032
- uses: julia-actions/julia-buildpkg@v1
4133
env:
4234
PYTHON:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Manifest.toml
22
docs/build
33
*.zarr
4+
.CondaPkg

Project.toml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,3 @@ OpenSSL = "1"
3333
URIs = "1"
3434
ZipArchives = "2"
3535
julia = "1.2"
36-
37-
[extras]
38-
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
39-
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
40-
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
41-
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
42-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
43-
44-
[targets]
45-
test = ["Test", "Conda", "PyCall", "Sockets", "Random"]

test/CondaPkg.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[deps]
2+
zarr = ">=2.13,<3"
3+
python = ">=3.7,<4"

test/Project.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
[deps]
2-
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
2+
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
33
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
44
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
55
Minio = "4281f0d9-7ae0-406e-9172-b7277c1efa20"
66
Mmap = "a63ad114-7e13-5084-954f-fe012c677804"
77
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
8-
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
8+
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
99
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1010
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
1111
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
12+
Zarr = "0a941bbe-ad1d-11e8-39d9-ab76183a1d99"
13+
14+
[sources]
15+
Zarr = {path = ".."}

test/python.jl

Lines changed: 107 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
@testset "Python zarr implementation" begin
77

88
import Mmap
9-
using PyCall
10-
import PyCall: @py_str
9+
using PythonCall
1110
#If we are on conda, import zarr
12-
pyimport_conda("zarr","zarr")
11+
zarr = pyimport("zarr")
1312

1413
#Create some directories
1514
proot = tempname()
@@ -82,113 +81,114 @@ end
8281

8382
# Test reading in python
8483
for julia_path in (pjulia, pjulia*".zip")
85-
py"""
86-
import zarr
87-
import numcodecs
88-
g = zarr.open_group($julia_path)
89-
gatts = g.attrs
90-
"""
84+
g = zarr.open_group(julia_path)
85+
gatts = pyconvert(Any, g.attrs)
9186

92-
#Test group attributes
93-
@test py"gatts['String attribute']" == "One"
94-
@test py"gatts['Int attribute']" == 5
95-
@test py"gatts['Float attribute']" == 10.5
87+
#Test group attributes
88+
@test gatts["String attribute"] == "One"
89+
@test gatts["Int attribute"] == 5
90+
@test gatts["Float attribute"] == 10.5
9691

97-
dtypesp = ("uint8","uint16","uint32","uint64",
98-
"int8","int16","int32","int64",
99-
"float16","float32","float64",
100-
"complex64", "complex128","bool","S10","U10", "O")
92+
dtypesp = ("uint8","uint16","uint32","uint64",
93+
"int8","int16","int32","int64",
94+
"float16","float32","float64",
95+
"complex64", "complex128","bool","S10","U10", "O")
10196

102-
#Test accessing arrays from python and reading data
103-
for i=1:length(dtypes), co in compressors
104-
compstr,comp = co
105-
t = dtypes[i]
106-
tp = dtypesp[i]
107-
arname = string("a",t,compstr)
108-
py"""
109-
ar=g[$arname]
110-
"""
97+
#Test accessing arrays from python and reading data
98+
for i=1:length(dtypes), co in compressors
99+
compstr,comp = co
100+
t = dtypes[i]
101+
tp = dtypesp[i]
102+
arname = string("a",t,compstr)
103+
ar=g[arname]
111104

112-
@test py"ar.attrs['This is a nested attribute']" == Dict("a"=>5)
113-
@test py"ar.dtype==$tp"
114-
@test py"ar.shape" == (2,6,10)
115-
if t<:MaxLengthString
116-
pyar = py"ar[:,:,:]"
117-
jar = [get(get(get(pyar,k-1),j-1),i-1) for i in 1:10, j in 1:6, k in 1:2]
118-
@test jar == testarrays[t]
119-
else
120-
@test py"ar[:,:,:]" == permutedims(testarrays[t],(3,2,1))
105+
@test pyconvert(Any, ar.attrs["This is a nested attribute"]) == Dict("a"=>5)
106+
@test pyeq(Bool, ar.dtype, tp)
107+
@test pyconvert(Tuple, ar.shape) == (2,6,10)
108+
if t<:MaxLengthString || t<:String
109+
jar = [
110+
if tp == "S10"
111+
pyconvert(String, ar[k, j, i].decode())
112+
else
113+
pyconvert(String, ar[k, j, i])
114+
end
115+
for i in 0:9, j in 0:5, k in 0:1
116+
]
117+
@test jar == testarrays[t]
118+
else
119+
@test PyArray(ar[pybuiltins.Ellipsis]) == permutedims(testarrays[t],(3,2,1))
120+
end
121121
end
122-
end
123122

124-
# Test reading filtered arrays from python
125-
for (filterstr, filter) in filters
126-
t = eltype(filter) == Any ? Float64 : eltype(filter)
127-
arname = string("filter_",filterstr)
128-
try
129-
py"""
130-
ar=g[$arname]
131-
"""
132-
catch e
133-
@error "Error loading group with filter $filterstr" exception=(e,catch_backtrace())
134-
@test false # test failed.
123+
# Test reading filtered arrays from python
124+
for (filterstr, filter) in filters
125+
t = eltype(filter) == Any ? Float64 : eltype(filter)
126+
arname = string("filter_",filterstr)
127+
local ar
128+
try
129+
ar=g[arname]
130+
catch e
131+
@error "Error loading group with filter $filterstr" exception=(e,catch_backtrace())
132+
@test false # test failed.
133+
end
134+
135+
@test pyconvert(Any, ar.attrs["Filter test attribute"]) == Dict("b"=>6)
136+
@test pyconvert(Tuple, ar.shape) == (2,6,10)
137+
138+
# Test zero-dimensional filtered array
139+
arname = string("filter_zerodim_",filterstr)
140+
ar_zero=g[arname]
141+
@test pyconvert(Tuple, ar_zero.shape) == ()
135142
end
136-
137-
@test py"ar.attrs['Filter test attribute']" == Dict("b"=>6)
138-
@test py"ar.shape" == (2,6,10)
139-
140-
# Test zero-dimensional filtered array
141-
arname = string("filter_zerodim_",filterstr)
142-
py"""
143-
ar_zero=g[$arname]
144-
"""
145-
@test py"ar_zero.shape" == ()
146-
end
147143

148-
for i=1:length(dtypes), co in compressors
149-
compstr,comp = co
150-
t = dtypes[i]
151-
tp = dtypesp[i]
152-
if t == UInt64
153-
continue
154-
# need to exclude UInt64:
155-
# need explicit conversion because of https://github.com/JuliaPy/PyCall.jl/issues/744
156-
# but explicit conversion uses PyLong_AsLongLongAndOverflow, which converts everything
157-
# to a signed 64-bit integer, which can error out if the UInt64 is too large.
158-
# Adding an overload to PyCall for unsigned ints doesn't work with NumPy scalars because
159-
# they are not subtypes of integer: https://stackoverflow.com/a/58816671
160-
end
144+
for i=1:length(dtypes), co in compressors
145+
compstr,comp = co
146+
t = dtypes[i]
147+
tp = dtypesp[i]
148+
if t == UInt64
149+
continue
150+
# need to exclude UInt64:
151+
# need explicit conversion because of https://github.com/JuliaPy/PyCall.jl/issues/744
152+
# but explicit conversion uses PyLong_AsLongLongAndOverflow, which converts everything
153+
# to a signed 64-bit integer, which can error out if the UInt64 is too large.
154+
# Adding an overload to PyCall for unsigned ints doesn't work with NumPy scalars because
155+
# they are not subtypes of integer: https://stackoverflow.com/a/58816671
156+
end
161157

162-
arname = string("azerodim",t,compstr)
163-
py"""
164-
ar=g[$arname]
165-
"""
158+
arname = string("azerodim",t,compstr)
159+
ar=g[arname]
166160

167-
@test py"ar.dtype==$tp"
168-
@test py"ar.shape" == ()
169-
@test convert(t, py"ar[()]") == testzerodimarrays[t]
170-
end
171-
py"""
172-
g.store.close()
173-
"""
161+
@test pyeq(Bool, ar.dtype, tp)
162+
@test pyconvert(Tuple, ar.shape) == ()
163+
if t<:MaxLengthString || t<:String
164+
local x = if tp == "S10"
165+
pyconvert(String, ar[()].decode())
166+
else
167+
pyconvert(String, ar[()])
168+
end
169+
@test x == testzerodimarrays[t]
170+
else
171+
@test pyconvert(Any, ar[()])[] == testzerodimarrays[t]
172+
end
173+
end
174+
g.store.close()
174175
end
175176

176177
## Now the other way around, we create a zarr array using the python lib and read back into julia
177178
data = rand(Int32,2,6,10)
178-
py"""
179-
import numcodecs
180-
import numpy as np
181-
g = zarr.group($ppython)
179+
180+
numpy = pyimport("numpy")
181+
numcodecs = pyimport("numcodecs")
182+
g = zarr.group(ppython)
182183
g.attrs["groupatt"] = "Hi"
183-
z1 = g.create_dataset("a1", shape=(2,6,10),chunks=(1,2,3), dtype='i4')
184-
z1[:,:,:]=$data
185-
z1.attrs["test"]={"b": 6}
186-
z2 = g.create_dataset("a2", shape=(5,),chunks=(5,), dtype='S1', compressor=numcodecs.Zlib())
187-
z2[:]=[k for k in 'hallo']
188-
z3 = g.create_dataset('a3', shape=(2,), dtype=str)
189-
z3[:]=np.asarray(['test1', 'test234'], dtype='O')
190-
zarr.consolidate_metadata($ppython)
191-
"""
184+
z1 = g.create_dataset("a1", shape=(2,6,10),chunks=(1,2,3), dtype="i4")
185+
z1[pybuiltins.Ellipsis] = numpy.array(data)
186+
z1.attrs["test"] = pydict(Dict("b"=>6))
187+
z2 = g.create_dataset("a2", shape=(5,),chunks=(5,), dtype="S1", compressor=numcodecs.Zlib())
188+
z2[pybuiltins.Ellipsis] = pylist([k for k in "hallo"])
189+
z3 = g.create_dataset("a3", shape=(2,), dtype=pybuiltins.str)
190+
z3[pybuiltins.Ellipsis]=numpy.asarray(["test1", "test234"], dtype="O")
191+
zarr.consolidate_metadata(ppython)
192192

193193
#Open in Julia
194194
g = zopen(ppython)
@@ -225,21 +225,17 @@ a1[:,1,1] = 1:10
225225

226226
# Test zip file can be read
227227
ppythonzip = ppython*".zip"
228-
py"""
229-
import numcodecs
230-
import numpy as np
231-
store = zarr.ZipStore($ppythonzip, mode="w")
228+
store = zarr.ZipStore(ppythonzip, mode="w")
232229
g = zarr.group(store=store)
233230
g.attrs["groupatt"] = "Hi"
234-
z1 = g.create_dataset("a1", shape=(2,6,10),chunks=(1,2,3), dtype='i4')
235-
z1[:,:,:]=$data
236-
z1.attrs["test"]={"b": 6}
237-
z2 = g.create_dataset("a2", shape=(5,),chunks=(5,), dtype='S1', compressor=numcodecs.Zlib())
238-
z2[:]=[k for k in 'hallo']
239-
z3 = g.create_dataset('a3', shape=(2,), dtype=str)
240-
z3[:]=np.asarray(['test1', 'test234'], dtype='O')
231+
z1 = g.create_dataset("a1", shape=(2,6,10),chunks=(1,2,3), dtype="i4")
232+
z1[pybuiltins.Ellipsis] = numpy.array(data)
233+
z1.attrs["test"] = pydict(Dict("b"=>6))
234+
z2 = g.create_dataset("a2", shape=(5,),chunks=(5,), dtype="S1", compressor=numcodecs.Zlib())
235+
z2[pybuiltins.Ellipsis] = pylist([k for k in "hallo"])
236+
z3 = g.create_dataset("a3", shape=(2,), dtype=pybuiltins.str)
237+
z3[pybuiltins.Ellipsis] = numpy.asarray(["test1", "test234"], dtype="O")
241238
store.close()
242-
"""
243239

244240
g = zopen(Zarr.ZipStore(Mmap.mmap(ppythonzip)))
245241
@test g isa Zarr.ZGroup
@@ -282,16 +278,15 @@ for pt in [Week, Day, Hour, Minute, Second,
282278
end
283279

284280
zarr = pyimport("zarr")
285-
np = pyimport("numpy")
286-
281+
numpy = pyimport("numpy")
287282
g_julia = zopen(p)
288283
g_python = zarr.open(p)
289284

290285
for unit in ["Week", "Day", "Hour", "Minute", "Second",
291286
"Millisecond"]
292-
@test_py np.datetime64(g_julia[unit][1] |> DateTime |> string) == get(getproperty(g_python,unit),0)
293-
@test_py np.datetime64(g_julia[unit][10] |> DateTime |> string) == get(getproperty(g_python,unit),9)
294-
@test_py np.datetime64(g_julia[unit][100] |> DateTime |> string) == get(getproperty(g_python,unit),99)
287+
for i in [0, 9, 99]
288+
@test pyeq(Bool, numpy.datetime64(g_julia[unit][i+1] |> DateTime |> string), g_python[unit][i])
289+
end
295290
end
296291

297292
end

test/runtests.jl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,11 @@ using Test
22
using Zarr
33
using JSON
44
using Pkg
5-
using PyCall
5+
using PythonCall
6+
using CondaPkg
67
using Dates
78

8-
macro test_py(ex)
9-
quote
10-
@test convert(Bool, $(esc(ex)))
11-
end
12-
end
9+
CondaPkg.add("zarr"; version="2.*")
1310

1411
@testset "Zarr" begin
1512

0 commit comments

Comments
 (0)