Skip to content

Commit 74b4421

Browse files
Build and test PyWavelets Pyodide wheels in CI (#701)
The main change is adding a Pyodide CI job. Other changes: * Add some relevant files and venvs to ignore * ensure file handles get closed and use importlib.resources instead of `__file__` * cache data loading * Convert `ecg` and `sst_nino3` data files from .npy to .npz (NumPy compressed archive) for Pyodide compatibility * Properly skip tests for WASM and wherever threading isn't availables * import `importlib.resources`, mark `/pywt/data/` as constant Co-authored-by: Ralf Gommers <[email protected]>
1 parent e69b126 commit 74b4421

File tree

9 files changed

+109
-19
lines changed

9 files changed

+109
-19
lines changed

.github/workflows/emscripten.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Test Pyodide build for PyWavelets
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- v1.**
8+
pull_request:
9+
branches:
10+
- master
11+
- v1.**
12+
13+
env:
14+
FORCE_COLOR: 3
15+
16+
jobs:
17+
build_wasm_emscripten:
18+
name: Build PyWavelets for Pyodide
19+
runs-on: ubuntu-latest
20+
# Uncomment the following line to test changes on a fork
21+
# if: github.repository == 'PyWavelets/pywt'
22+
steps:
23+
- name: Check out repository
24+
uses: actions/checkout@v4
25+
26+
- name: Set up Python 3.11
27+
id: setup-python
28+
uses: actions/setup-python@v2
29+
with:
30+
python-version: '3.11.2'
31+
32+
- name: Install prerequisites
33+
run: |
34+
python -m pip install pyodide-build "pydantic<2"
35+
echo EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version) >> $GITHUB_ENV
36+
37+
- name: Set up Emscripten toolchain
38+
uses: mymindstorm/setup-emscripten@v14
39+
with:
40+
version: ${{ env.EMSCRIPTEN_VERSION }}
41+
actions-cache-folder: emsdk-cache
42+
43+
- name: Set up Node.js
44+
uses: actions/[email protected]
45+
with:
46+
node-version: '18'
47+
48+
- name: Build PyWavelets
49+
run: |
50+
pyodide build
51+
52+
- name: Install and test wheel
53+
run: |
54+
pyodide venv .venv-pyodide
55+
source .venv-pyodide/bin/activate
56+
pip install dist/*.whl
57+
pushd demo
58+
pip install matplotlib pytest
59+
python -c "import pywt; print(pywt.__version__)"
60+
pytest --pyargs pywt

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ __pycache__
22
*.py[co]
33
*.pyd
44
*.so
5+
.DS_Store
6+
.pytest_cache/
57

68
# Packages
79
*.egg
@@ -32,6 +34,12 @@ cythonize.dat
3234
pywt/version.py
3335
build.log
3436

37+
# Virtual environments
38+
.env/
39+
env/
40+
venv/
41+
.venv/
42+
3543
# asv files
3644
asv/env
3745
asv/html

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ For more usage examples see the `demo`_ directory in the source package.
6565
Installation
6666
------------
6767

68-
PyWavelets supports `Python`_ >=3.7, and is only dependent on `NumPy`_
68+
PyWavelets supports `Python`_ >=3.9, and is only dependent on `NumPy`_
6969
(supported versions are currently ``>= 1.14.6``). To pass all of the tests,
7070
`Matplotlib`_ is also required. `SciPy`_ is also an optional dependency. When
7171
present, FFT-based continuous wavelet transforms will use FFTs from SciPy

demo/batch_processing.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
from concurrent import futures
2525
except ImportError:
2626
raise ImportError(
27-
"This demo requires concurrent.futures. It can be installed for "
28-
"for python 2.x via: pip install futures")
27+
"This demo requires concurrent.futures. If you are on WebAssembly, this is not available.")
2928

3029
import numpy as np
3130
from numpy.testing import assert_array_equal

pywt/_pytest.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""common test-related code."""
22
import os
33
import sys
4+
import platform
45
import multiprocessing
56
import numpy as np
67
import pytest
@@ -18,15 +19,18 @@
1819
]
1920

2021
try:
21-
if sys.version_info[0] == 2:
22-
import futures
23-
else:
24-
from concurrent import futures
22+
from concurrent import futures
2523
max_workers = multiprocessing.cpu_count()
2624
futures_available = True
2725
except ImportError:
2826
futures_available = False
2927
futures = None
28+
max_workers = 1
29+
30+
# Check if running on Emscripten/WASM, and skip tests that require concurrency.
31+
# Relevant issue: https://github.com/pyodide/pyodide/issues/237
32+
IS_WASM = (sys.platform == "emscripten") or (platform.machine() in ["wasm32", "wasm64"])
33+
3034

3135
# check if pymatbridge + MATLAB tests should be run
3236
matlab_result_dict_dwt = None
@@ -57,7 +61,9 @@
5761
matlab_result_dict_dwt = np.load(matlab_data_file_dwt)
5862

5963
uses_futures = pytest.mark.skipif(
60-
not futures_available, reason='futures not available')
64+
not futures_available or IS_WASM,
65+
reason='futures is not available, or running via Pyodide/WASM.')
66+
# not futures_available, reason='futures not available')
6167
uses_matlab = pytest.mark.skipif(
6268
matlab_missing, reason='pymatbridge and/or Matlab not available')
6369
uses_pymatbridge = pytest.mark.skipif(

pywt/data/_readers.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
import functools
2+
import importlib.resources
13
import os
24

35
import numpy as np
46

57

8+
_DATADIR = importlib.resources.files('pywt.data')
9+
10+
11+
@functools.cache
612
def ascent():
713
"""
814
Get an 8-bit grayscale bit-depth, 512 x 512 derived image for
@@ -36,11 +42,13 @@ def ascent():
3642
>>> plt.show() # doctest: +SKIP
3743
3844
"""
39-
fname = os.path.join(os.path.dirname(__file__), 'ascent.npz')
40-
ascent = np.load(fname)['data']
45+
with importlib.resources.as_file(_DATADIR.joinpath('ascent.npz')) as f:
46+
ascent = np.load(f)['data']
47+
4148
return ascent
4249

4350

51+
@functools.cache
4452
def aero():
4553
"""
4654
Get an 8-bit grayscale bit-depth, 512 x 512 derived image for
@@ -71,11 +79,13 @@ def aero():
7179
>>> plt.show() # doctest: +SKIP
7280
7381
"""
74-
fname = os.path.join(os.path.dirname(__file__), 'aero.npz')
75-
aero = np.load(fname)['data']
82+
with importlib.resources.as_file(_DATADIR.joinpath('aero.npz')) as f:
83+
aero = np.load(f)['data']
84+
7685
return aero
7786

7887

88+
@functools.cache
7989
def camera():
8090
"""
8191
Get an 8-bit grayscale bit-depth, 512 x 512 derived image for
@@ -117,11 +127,13 @@ def camera():
117127
>>> plt.show() # doctest: +SKIP
118128
119129
"""
120-
fname = os.path.join(os.path.dirname(__file__), 'camera.npz')
121-
camera = np.load(fname)['data']
130+
with importlib.resources.as_file(_DATADIR.joinpath('camera.npz')) as f:
131+
camera = np.load(f)['data']
132+
122133
return camera
123134

124135

136+
@functools.cache
125137
def ecg():
126138
"""
127139
Get 1024 points of an ECG timeseries.
@@ -147,11 +159,13 @@ def ecg():
147159
[<matplotlib.lines.Line2D object at ...>]
148160
>>> plt.show() # doctest: +SKIP
149161
"""
150-
fname = os.path.join(os.path.dirname(__file__), 'ecg.npy')
151-
ecg = np.load(fname)
162+
with importlib.resources.as_file(_DATADIR.joinpath('ecg.npz')) as f:
163+
ecg = np.load(f)['data']
164+
152165
return ecg
153166

154167

168+
@functools.cache
155169
def nino():
156170
"""
157171
This data contains the averaged monthly sea surface temperature in degrees
@@ -183,8 +197,9 @@ def nino():
183197
[<matplotlib.lines.Line2D object at ...>]
184198
>>> plt.show() # doctest: +SKIP
185199
"""
186-
fname = os.path.join(os.path.dirname(__file__), 'sst_nino3.npy')
187-
sst_csv = np.load(fname)
200+
with importlib.resources.as_file(_DATADIR.joinpath('sst_nino3.npz')) as f:
201+
sst_csv = np.load(f)['data']
202+
188203
# sst_csv = pd.read_csv("http://www.cpc.ncep.noaa.gov/data/indices/ersst4.nino.mth.81-10.ascii", sep=' ', skipinitialspace=True)
189204
# take only full years
190205
n = int(np.floor(sst_csv.shape[0]/12.)*12.)
Binary file not shown.
Binary file not shown.

pywt/tests/test_concurrent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import warnings
99
import numpy as np
1010
from functools import partial
11+
12+
import pytest
1113
from numpy.testing import assert_array_equal, assert_allclose
12-
from pywt._pytest import uses_futures, futures, max_workers
1314

15+
from pywt._pytest import uses_futures, futures, max_workers
1416
import pywt
1517

1618

0 commit comments

Comments
 (0)