diff --git a/.flake8 b/.flake8 index 75d2e1a211..fc7bb54da2 100644 --- a/.flake8 +++ b/.flake8 @@ -30,3 +30,7 @@ per-file-ignores = # https://stackoverflow.com/questions/59167405/) __init__.py: F401 */__init__.py: F401 + # Whole-module test skips (via setUp method) for these cases means exit + # before reach undefined vars where need given module for defining those. + # This is the cleanest way but sets off F821 code en-masse so ignore that. + cf/test/test_HEALPix_utils.py: F821 diff --git a/cf/test/test_Domain.py b/cf/test/test_Domain.py index 15e492b140..59c69c0f22 100644 --- a/cf/test/test_Domain.py +++ b/cf/test/test_Domain.py @@ -1,6 +1,7 @@ import datetime import re import unittest +from importlib.util import find_spec import numpy as np @@ -494,6 +495,9 @@ def test_Domain_cyclic_iscyclic(self): d2.cyclic("X", iscyclic=False) self.assertTrue(d2.iscyclic("X")) + # Note: here only need healpix for cf under-the-hood code, not in test + # directly, so no need to actually import healpix, just test it is there. + @unittest.skipUnless(find_spec("healpix"), "Requires 'healpix' package.") def test_Domain_create_healpix(self): """Test Domain.create_healpix.""" d = cf.Domain.create_healpix(0) diff --git a/cf/test/test_Field.py b/cf/test/test_Field.py index 8b60ba5b06..d399f3e18f 100644 --- a/cf/test/test_Field.py +++ b/cf/test/test_Field.py @@ -16,6 +16,12 @@ import cf +healpix_available = False +# Note: here only need healpix for cf under-the-hood code, not in test +# directly, so no need to actually import healpix, just test it is there. +if find_spec("healpix"): + healpix_available = True + n_tmpfiles = 1 tmpfiles = [ tempfile.mkstemp("_test_Field.nc", dir=os.getcwd())[1] @@ -3137,6 +3143,7 @@ def test_Field_to_units(self): with self.assertRaises(ValueError): g.to_units("degC") + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_healpix_change_indexing_scheme(self): """Test Field.healpix_change_indexing_scheme.""" # HEALPix field @@ -3236,6 +3243,7 @@ def test_Field_healpix_change_indexing_scheme(self): with self.assertRaises(ValueError): self.f0.healpix_change_indexing_scheme("ring") + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_healpix_to_ugrid(self): """Test Field.healpix_to_ugrid.""" # HEALPix field @@ -3276,6 +3284,7 @@ def test_Field_healpix_to_ugrid(self): with self.assertRaises(ValueError): self.f0.healpix_to_ugrid() + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_create_latlon_coordinates(self): """Test Field.create_latlon_coordinates.""" # ------------------------------------------------------------ @@ -3334,6 +3343,7 @@ def test_Field_create_latlon_coordinates(self): self.assertTrue(mc[:16].equals(l2.auxiliary_coordinate(c)[:16])) self.assertTrue(mc[16:].equals(l1.auxiliary_coordinate(c)[4:])) + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_healpix_subspace(self): """Test Field.subspace for HEALPix grids""" f = self.f12 @@ -3369,6 +3379,7 @@ def test_Field_healpix_subspace(self): np.array_equal(g.coordinate("healpix_index"), [13, 12]) ) + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_healpix_decrease_refinement_level(self): """Test Field.healpix_decrease_refinement_level.""" f = self.f12 @@ -3469,6 +3480,7 @@ def my_mean(a, axis=None): with self.assertRaises(ValueError): self.f0.healpix_decrease_refinement_level(0, "mean") + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_Field_healpix_increase_refinement_level(self): """Test Field.healpix_increase_refinement_level.""" f = self.f12 diff --git a/cf/test/test_HEALPix_utils.py b/cf/test/test_HEALPix_utils.py index bec272cda6..514b9f7014 100644 --- a/cf/test/test_HEALPix_utils.py +++ b/cf/test/test_HEALPix_utils.py @@ -1,33 +1,49 @@ import datetime import unittest -import healpix import numpy as np import cf -# Create matching lists of selected nested, ring, nuniq and zuniq -# indices for every refinement level. -indices = [ - (r, i, healpix.nest2ring(healpix.order2nside(r), i)) - for r in range(30) - for i in (0, 7, (12 * 4**r) - 1) -] -refinement_levels, nested_indices, ring_indices = map(list, zip(*indices)) - -nuniq_indices = [ - i + 4 ** (1 + r) for r, i in zip(refinement_levels, nested_indices) -] - -zuniq_indices = [ - (2 * i + 1) * 4 ** (29 - r) - for r, i in zip(refinement_levels, nested_indices) -] +healpix_imported = True +try: + import healpix # noqa: F401 +except ImportError: + healpix_imported = False class DataTest(unittest.TestCase): """Unit tests for HEALPix utilities.""" + def setUp(self): + """Preparations called immediately before each test method.""" + # Skip all if healpix module not available! + if not healpix_imported: + self.skipTest( + "Test module requires 'healpix' package. Install it to run all." + ) + else: + # Create matching lists of selected nested, ring, nuniq and zuniq + # indices for every refinement level. + indices = [ + (r, i, healpix.nest2ring(healpix.order2nside(r), i)) + for r in range(30) + for i in (0, 7, (12 * 4**r) - 1) + ] + refinement_levels, nested_indices, ring_indices = map( + list, zip(*indices) + ) + + nuniq_indices = [ # noqa: F841 + i + 4 ** (1 + r) + for r, i in zip(refinement_levels, nested_indices) + ] + + zuniq_indices = [ # noqa: F841 + (2 * i + 1) * 4 ** (29 - r) + for r, i in zip(refinement_levels, nested_indices) + ] + def test_HEALPix_uniq2zuniq(self): """Test _uniq2zuniq""" from cf.data.dask_utils_healpix import _uniq2zuniq diff --git a/cf/test/test_RegridOperator.py b/cf/test/test_RegridOperator.py index 28cf79f215..0f062300ce 100644 --- a/cf/test/test_RegridOperator.py +++ b/cf/test/test_RegridOperator.py @@ -19,11 +19,16 @@ class RegridOperatorTest(unittest.TestCase): def setUp(self): - src = cf.example_field(0) - dst = cf.example_field(1) - self.r = src.regrids(dst, "linear", return_operator=True) + # Skip all if espmy module not available! + if not esmpy_imported: + self.skipTest( + "Test module requires 'esmpy' package. Install it to run all." + ) + else: + src = cf.example_field(0) + dst = cf.example_field(1) + self.r = src.regrids(dst, "linear", return_operator=True) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_RegridOperator_attributes(self): self.assertEqual(self.r.coord_sys, "spherical") self.assertEqual(self.r.method, "linear") @@ -51,7 +56,6 @@ def test_RegridOperator_attributes(self): self.assertIsNone(self.r.dst_z) self.assertFalse(self.r.ln_z) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_RegridOperator_copy(self): self.assertIsInstance(self.r.copy(), self.r.__class__) diff --git a/cf/test/test_collapse.py b/cf/test/test_collapse.py index 2dde8679c5..b3f73f4524 100644 --- a/cf/test/test_collapse.py +++ b/cf/test/test_collapse.py @@ -4,6 +4,7 @@ import os import tempfile import unittest +from importlib.util import find_spec import numpy as np @@ -825,6 +826,9 @@ def test_Field_collapse_ugrid(self): # Check the collpsed fields writes cf.write(f, tmpfile) + # Note: here only need healpix for cf under-the-hood code, not in test + # directly, so no need to actually import healpix, just test it is there. + @unittest.skipUnless(find_spec("healpix"), "Requires 'healpix' package.") def test_Field_collapse_HEALPix(self): """Test HEALPix collapses.""" f0 = cf.example_field(12) diff --git a/cf/test/test_functions.py b/cf/test/test_functions.py index 98d186d02a..b686a98b40 100644 --- a/cf/test/test_functions.py +++ b/cf/test/test_functions.py @@ -4,6 +4,7 @@ import platform import sys import unittest +from importlib.util import find_spec import dask.array as da import numpy as np @@ -487,6 +488,9 @@ def test_normalize_slice(self): with self.assertRaises(IndexError): cf.normalize_slice(index, 8, cyclic=True) + # Note: here only need healpix for cf under-the-hood code, not in test + # directly, so no need to actually import healpix, just test it is there. + @unittest.skipUnless(find_spec("healpix"), "Requires 'healpix' package.") def test_locate(self): """Test cf.locate""" # HEALPix diff --git a/cf/test/test_regrid.py b/cf/test/test_regrid.py index f5d4675ca4..236d852414 100644 --- a/cf/test/test_regrid.py +++ b/cf/test/test_regrid.py @@ -11,6 +11,12 @@ import cf +esmpy_imported = True +try: + import esmpy # noqa: F401 +except ImportError: + esmpy_imported = False + n_tmpfiles = 1 tmpfiles = [ tempfile.mkstemp("_test_regrid.nc", dir=os.getcwd())[1] @@ -30,14 +36,6 @@ def _remove_tmpfiles(): atexit.register(_remove_tmpfiles) - -esmpy_imported = True -try: - import esmpy # noqa: F401 -except ImportError: - esmpy_imported = False - - all_methods = ( "linear", "conservative", @@ -140,19 +138,27 @@ def esmpy_regrid_Nd(coord_sys, method, src, dst, **kwargs): class RegridTest(unittest.TestCase): - # Get the test source and destination fields - filename = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "regrid.nc" - ) - dst_src = cf.read(filename) - dst = dst_src[0] - src = dst_src[1] - filename_xyz = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "regrid_xyz.nc" - ) + def setUp(self): + """Preparations called immediately before each test method.""" + # Skip all if esmpy module not available! + if not esmpy_imported: + self.skipTest( + "Test module requires 'esmpy' package. Install it to run all." + ) + else: + # Get the test source and destination fields + filename = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "regrid.nc" + ) + dst_src = cf.read(filename) + dst = dst_src[0] # noqa: F841 + src = dst_src[1] # noqa: F841 + + filename_xyz = os.path.join( # noqa: F841 + os.path.dirname(os.path.abspath(__file__)), "regrid_xyz.nc" + ) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrid_2d_field(self): """2-d regridding with Field destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -304,7 +310,6 @@ def test_Field_regrid_2d_field(self): with self.assertRaises(ValueError): src.regrids(dst, method=method).array - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrids_coords(self): """Spherical regridding with coords destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -374,7 +379,6 @@ def test_Field_regrids_coords(self): d1 = src.regrids(r) self.assertTrue(d1.data.equals(d0.data, atol=atol, rtol=rtol)) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regridc_2d_coords(self): """2-d Cartesian regridding with coords destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -418,7 +422,6 @@ def test_Field_regrids_bad_dst(self): with self.assertRaises(ValueError): self.src.regrids("foobar", method="conservative") - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrids_domain(self): """Spherical regridding with Domain destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -442,7 +445,6 @@ def test_Field_regrids_domain(self): d1 = src.regrids(r) self.assertTrue(d1.equals(d0, atol=atol, rtol=rtol)) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regridc_domain(self): """Spherical regridding with Domain destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -468,7 +470,6 @@ def test_Field_regridc_domain(self): d1 = src.regridc(r) self.assertTrue(d1.equals(d0, atol=atol, rtol=rtol)) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrids_field_operator(self): """Spherical regridding with operator destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -506,7 +507,6 @@ def test_Field_regrids_field_operator(self): with self.assertRaises(ValueError): dst.regrids(r) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrids_non_coordinates(self): """Check setting of non-coordinate metadata.""" self.assertFalse(cf.regrid_logging()) @@ -555,7 +555,6 @@ def test_Field_regrids_non_coordinates(self): # Cell measures self.assertFalse(d1.cell_measures()) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regridc_3d_field(self): """3-d Cartesian regridding with Field destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -659,7 +658,6 @@ def test_Field_regridc_3d_field(self): with self.assertRaises(ValueError): src.regridc(dst, method=method, axes=axes) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regridc_1d_field(self): """1-d Cartesian regridding with Field destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -750,7 +748,6 @@ def test_Field_regridc_1d_field(self): with self.assertRaises(ValueError): src.regridc(dst, method=method, axes=axes) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regridc_1d_coordinates_z(self): """1-d Z Cartesian regridding with coordinates destination grid.""" self.assertFalse(cf.regrid_logging()) @@ -763,7 +760,6 @@ def test_Field_regridc_1d_coordinates_z(self): z = d.dimension_coordinate("Z") self.assertTrue(z.data.equals(dst.data)) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrid_chunks(self): """Regridding of chunked axes""" self.assertFalse(cf.regrid_logging()) @@ -780,7 +776,6 @@ def test_Field_regrid_chunks(self): d0 = src.regrids(dst, method="linear") self.assertEqual(d0.data.numblocks, (1, 1, 1)) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_Field_regrid_weights_file(self): """Regridding creation/use of weights file""" self.assertFalse(cf.regrid_logging()) @@ -816,7 +811,6 @@ def test_Field_regrid_weights_file(self): src.regrids(r1, method="linear", weights_file=tmpfile) ) - @unittest.skipUnless(esmpy_imported, "Requires esmpy/ESMF package.") def test_return_esmpy_regrid_operator(self): """esmpy regrid operator returns esmpy.Regrid in regrids and regridc""" self.assertFalse(cf.regrid_logging()) diff --git a/cf/test/test_weights.py b/cf/test/test_weights.py index 5043a6b796..90e4ebd70f 100644 --- a/cf/test/test_weights.py +++ b/cf/test/test_weights.py @@ -1,10 +1,17 @@ import datetime import unittest +from importlib.util import find_spec import numpy as np import cf +healpix_available = False +# Note: here only need healpix for cf under-the-hood code, not in test +# directly, so no need to actually import healpix, just test it is there. +if find_spec("healpix"): + healpix_available = True + # A radius greater than 1. Used since weights based on the unit # sphere and non-spheres are tested separately. r = 2 @@ -152,6 +159,7 @@ def test_weights_polygon_area_geometry(self): self.assertTrue((w.array == correct_weights).all()) self.assertEqual(w.Units, cf.Units("m2")) + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_weights_polygon_area_ugrid(self): f = cf.example_field(8) f = f[..., [0, 2]] @@ -345,6 +353,7 @@ def test_weights_exceptions(self): ): f.weights("area") + @unittest.skipUnless(healpix_available, "Requires 'healpix' package.") def test_weights_healpix(self): """Test HEALPix weights.""" # HEALPix grid with Multi-Order Coverage (a combination of