diff --git a/.github/workflows/test_code_generation.yml b/.github/workflows/test_code_generation.yml new file mode 100644 index 0000000..caea97c --- /dev/null +++ b/.github/workflows/test_code_generation.yml @@ -0,0 +1,48 @@ +name: Test code generation + +on: + pull_request: + push: + branches: [main] + +jobs: + code-generation: + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + + steps: + - name: checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Micromamba + uses: mamba-org/setup-micromamba@v1 + with: + environment-name: TEST + init-shell: bash + create-args: >- + python=3 pip + --file requirements-dev.txt + --channel conda-forge + + - name: Install nightly version of numpy + run: | + python -m pip install --pre --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --extra-index-url https://pypi.org/simple numpy scipy pandas -U + + - name: Test Code Generation + run: > + git clone https://github.com/TEOS-10/GSW-C.git ../GSW-C + && git clone https://github.com/TEOS-10/GSW-Matlab.git ../GSW-Matlab + && python tools/copy_from_GSW-C.py + && python tools/mat2npz.py + && python tools/make_ufuncs.py + && python tools/make_wrapped_ufuncs.py + && python tools/fix_wrapped_ufunc_typos.py + + - name: Install gsw + run: > + python -m pip install -v -e . --no-deps --no-build-isolation --force-reinstall + && python -m pytest -s -rxs -v gsw/tests \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 015e064..4d7a5f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,13 +12,21 @@ jobs: matrix: python-version: ["3.9", "3.10", "3.11", "3.12"] os: [windows-latest, ubuntu-latest, macos-latest] + experimental: [false] # Oldest one based on NEP-29 and latest one. # See https://numpy.org/neps/nep-0029-deprecation_policy.html numpy-version: ["1.23", "1.26"] exclude: - python-version: "3.12" numpy-version: "1.23" + include: + - python-version: "3.12" + os: "ubuntu-latest" + experimental: true fail-fast: false + defaults: + run: + shell: bash -l {0} steps: - uses: actions/checkout@v4 @@ -31,18 +39,21 @@ jobs: create-args: >- python=${{ matrix.python-version }} python-build numpy=${{ matrix.numpy-version }} --file requirements-dev.txt --channel conda-forge - - name: Install gsw - shell: bash -l {0} + - name: Install unstable dependencies + if: matrix.experimental == true run: | - python -m pip install -e . --no-deps --force-reinstall + python -m pip install \ + --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/ \ + --trusted-host pypi.anaconda.org \ + --no-deps --pre --upgrade \ + numpy scipy pandas; + python -m pip install -v -e . --no-deps --no-build-isolation --force-reinstall - - name: Debug - shell: bash -l {0} + - name: Install gsw + if: matrix.experimental != true run: | - python -c "import numpy; print(f'Running numpy {numpy.__version__}')" + python -m pip install -e . --no-deps --force-reinstall - name: Tests - shell: bash -l {0} run: | - micromamba activate TEST - pytest -s -rxs -v gsw/tests + python -m pytest -s -rxs -v gsw/tests diff --git a/gsw/tests/test_check_functions.py b/gsw/tests/test_check_functions.py index 103568d..7268fac 100644 --- a/gsw/tests/test_check_functions.py +++ b/gsw/tests/test_check_functions.py @@ -45,7 +45,7 @@ mfuncnames = [mf.name for mf in mfuncs] -@pytest.fixture(params=[-360, 0, 360]) +@pytest.fixture(params=[-360., 0., 360.]) def lonshift(request): return request.param diff --git a/pyproject.toml b/pyproject.toml index 37755c7..99f9cf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,8 @@ build-backend = "setuptools.build_meta" requires = [ "build", - "oldest-supported-numpy", + 'numpy<3,>=2.0.0rc1; python_version >= "3.9"', + 'oldest-supported-numpy; python_version < "3.9"', "pip>9.0.1", "setuptools>=42", "setuptools_scm[toml]>=3.4", @@ -60,18 +61,19 @@ write_to_template = "__version__ = '{version}'" tag_regex = "^(?Pv)?(?P[^\\+]+)(?P.*)?$" [tool.ruff] -select = [ +lint.select = [ "A", # flake8-builtins "B", # flake8-bugbear "C4", # flake8-comprehensions "F", # flakes "I", # import sorting "UP", # upgrade + "NPY201", # numpy 2.0 ] target-version = "py38" line-length = 105 -ignore = [ +lint.ignore = [ "F401", # module imported but unused "E501", # line too long "E713", # test for membership should be 'not in' @@ -81,7 +83,7 @@ exclude = [ "tools", ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "docs/conf.py" = [ "A001", # variable is shadowing a python builtin ] diff --git a/requirements-dev.txt b/requirements-dev.txt index e1d1376..dbeb3a3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,9 +1,11 @@ check-manifest +dask numpydoc +pandas>=2 pytest +scipy setuptools_scm sphinx sphinx_rtd_theme twine -pandas>=2 -dask +xarray \ No newline at end of file diff --git a/setup.py b/setup.py index 6924b1b..4e08805 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ import shutil import sys -import pkg_resources +import numpy from setuptools import Extension, setup from setuptools.command.build_ext import build_ext as _build_ext @@ -21,7 +21,7 @@ def read(*parts): class build_ext(_build_ext): # Extension builder from pandas without the cython stuff def build_extensions(self): - numpy_incl = pkg_resources.resource_filename("numpy", "core/include") + numpy_incl = numpy.get_include() for ext in self.extensions: if hasattr(ext, "include_dirs") and not numpy_incl in ext.include_dirs: diff --git a/tools/c_header_parser.py b/tools/c_header_parser.py index 8007a40..1a03007 100644 --- a/tools/c_header_parser.py +++ b/tools/c_header_parser.py @@ -8,7 +8,7 @@ import numpy as np -basedir = Path('..').resolve() +basedir = Path(__file__).parent.parent def get_signatures(strip_extern=True, srcdir='src'): """ diff --git a/tools/copy_from_GSW-C.py b/tools/copy_from_GSW-C.py index d08fbe6..af10a28 100644 --- a/tools/copy_from_GSW-C.py +++ b/tools/copy_from_GSW-C.py @@ -3,26 +3,31 @@ Copy all relevant .c and .h files from Python-C, if they are newer. This is a simple utility, to be run from this directory. It assumes that -an up-to-date GSW-C github repo and the present GSW-Python repo are +an up-to-date GSW-C GitHub repo and the present GSW-Python repo are siblings in the directory tree. """ +import sys import shutil from pathlib import Path +current = Path(__file__).parent +srcdir = Path(current.parent.parent, "GSW-C") -fnames = ['gsw_oceanographic_toolbox.c', - 'gsw_saar.c', - 'gsw_saar_data.h', - 'gsw_internal_const.h', - 'gswteos-10.h'] +destdir = Path(current.parent, "src", "c_gsw") + +fnames = [ + "gsw_oceanographic_toolbox.c", + "gsw_saar.c", + "gsw_saar_data.h", + "gsw_internal_const.h", + "gswteos-10.h", + ] -srcdir = Path('..', '..', 'GSW-C') -destdir = Path('..', 'src', 'c_gsw') if not srcdir.exists(): raise IOError( - f"Could not find the GSW-C source code in {srcdir}." + f"Could not find the GSW-C source code in {srcdir}. " "Please read the development notes to find how to setup your GSW-Python development environment." ) @@ -31,4 +36,4 @@ dest = destdir.joinpath(fname) if src.stat().st_mtime > dest.stat().st_mtime: shutil.copyfile(str(src), str(dest)) - print('copied %s to %s' % (src, dest)) + print(f"copied {src} to {dest}") diff --git a/tools/fix_wrapped_ufunc_typos.py b/tools/fix_wrapped_ufunc_typos.py index 7a43bf1..2e49527 100644 --- a/tools/fix_wrapped_ufunc_typos.py +++ b/tools/fix_wrapped_ufunc_typos.py @@ -9,7 +9,7 @@ from pathlib import Path import shutil -basedir = Path('..').resolve() +basedir = Path(__file__).parent.parent wrapmod = basedir.joinpath('gsw', '_wrapped_ufuncs.py') orig = wrapmod.with_suffix('.orig') diff --git a/tools/make_ufuncs.py b/tools/make_ufuncs.py index 85e4427..5540957 100644 --- a/tools/make_ufuncs.py +++ b/tools/make_ufuncs.py @@ -14,7 +14,7 @@ blacklist = ['add_barrier'] -basedir = Path('..').resolve() +basedir = Path(__file__).parent.parent modfile_head_top = """ /* @@ -358,19 +358,19 @@ def write_modfile(modfile_name, srcdir): f.write(''.join(chunks)) funcnamelist1.sort() - with open(srcdir + '_ufuncs1.list', 'w') as f: + with open(srcdir.joinpath('_ufuncs1.list'), 'w') as f: f.write('\n'.join(funcnamelist1)) funcnamelist2.sort() - with open(srcdir + '_ufuncs2.list', 'w') as f: + with open(srcdir.joinpath('_ufuncs2.list'), 'w') as f: f.write('\n'.join(funcnamelist2)) funcnamelist = funcnamelist1 + funcnamelist2 + funcnamelist3 funcnamelist.sort() - with open(srcdir + '_ufuncs.list', 'w') as f: + with open(srcdir.joinpath('_ufuncs.list'), 'w') as f: f.write('\n'.join(funcnamelist)) if __name__ == '__main__': - srcdir = 'src' + srcdir = basedir.joinpath('src') modfile_name = basedir.joinpath(srcdir, '_ufuncs.c') write_modfile(modfile_name, srcdir=srcdir) diff --git a/tools/make_wrapped_ufuncs.py b/tools/make_wrapped_ufuncs.py index 17c32de..f89cd6c 100644 --- a/tools/make_wrapped_ufuncs.py +++ b/tools/make_wrapped_ufuncs.py @@ -16,7 +16,7 @@ fix_outputs_doc, docstring_from_sections) -basedir = Path('..').resolve() +basedir = Path(__file__).parent.parent # Functions that are Matlab subroutines, or exclusive to @@ -205,8 +205,8 @@ def uf_wrapper(ufname): return wrapper_template % subs if __name__ == '__main__': - srcdir = 'src' - with open(srcdir + '_ufuncs.list') as f: + srcdir = basedir.joinpath('src') + with open(srcdir.joinpath('_ufuncs.list')) as f: ufunclist = [name.strip() for name in f.readlines()] ufunclist = [name for name in ufunclist if name not in blacklist] @@ -232,5 +232,5 @@ def uf_wrapper(ufname): f.write(wrapped) wrapped_ufnames.append(ufname) wrapped_ufnames.sort() - with open(srcdir + '_wrapped_ufuncs.list', 'w') as f: + with open(srcdir.joinpath('_wrapped_ufuncs.list'), 'w') as f: f.write('\n'.join(wrapped_ufnames) + '\n') diff --git a/tools/mat2npz.py b/tools/mat2npz.py index 4d4b291..f347c3b 100644 --- a/tools/mat2npz.py +++ b/tools/mat2npz.py @@ -45,8 +45,14 @@ def loadmatdict(fname): # The following relative path will depend on the directory layout for # whoever is running this utility. -gsw_data_file = Path('..', '..', '..', 'gsw_matlab_%s' % mat_zip_ver, - 'library', 'gsw_data_%s.mat' % data_ver) +basedir = Path(__file__).parent.parent +gsw_data_file = Path( + basedir.parent, + "GSW-Matlab", + "Toolbox", + "library", + f"gsw_data_{data_ver}.mat", + ) print(gsw_data_file) gsw_data = loadmatdict(gsw_data_file) @@ -55,5 +61,5 @@ def loadmatdict(fname): cv_vars = gsw_data['gsw_cv'] cv_vars['gsw_data_file'] = str(gsw_data_file) cv_vars['mat_zip_ver'] = mat_zip_ver -fname = Path('..', 'gsw', 'tests', 'gsw_cv_%s' % data_ver) +fname = Path(basedir, "gsw", "tests", f"gsw_cv_{data_ver}") np.savez(str(fname), **cv_vars) diff --git a/tools/matlab_parser.py b/tools/matlab_parser.py index cc6392c..a49d21a 100644 --- a/tools/matlab_parser.py +++ b/tools/matlab_parser.py @@ -5,8 +5,7 @@ import re from pathlib import Path - -basedir = Path('..').resolve() +basedir = Path(__file__).parent.parent gsw_matlab_dir = basedir.joinpath('..', 'GSW-Matlab', 'Toolbox').resolve() if not gsw_matlab_dir.exists():