Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/iris/fileformats/netcdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,7 @@ def __init__(self, filename, netcdf_format):
#: A dictionary, mapping formula terms to owner cf variable name
self._formula_terms_cache = {}
#: NetCDF dataset
self._filename = filename
try:
self._dataset = netCDF4.Dataset(
filename, mode="w", format=netcdf_format
Expand Down
37 changes: 29 additions & 8 deletions lib/iris/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,19 +388,40 @@ def assertCDL(self, netcdf_filename, reference_filename=None, flags="-h"):
re_ncprop = re.compile(r"^\s*:_NCProperties *=")
lines = [line for line in lines if not re_ncprop.match(line)]

# Sort the dimensions (except for the first, which can be unlimited).
# This gives consistent CDL across different platforms.
def sort_key(line):
return ("UNLIMITED" not in line, line)
if "dimensions:" in lines:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting fix, maybe useful to adopt generally :
Without it, assertCDL fails on a saved scalar cube (i.e. which has no dims)

# Sort the dimensions (except for the first, which can be unlimited).
# This gives consistent CDL across different platforms.
def sort_key(line):
return ("UNLIMITED" not in line, line)

dimension_lines = slice(
lines.index("dimensions:") + 1, lines.index("variables:")
)
lines[dimension_lines] = sorted(
lines[dimension_lines], key=sort_key
)

dimension_lines = slice(
lines.index("dimensions:") + 1, lines.index("variables:")
)
lines[dimension_lines] = sorted(lines[dimension_lines], key=sort_key)
cdl = "\n".join(lines) + "\n"

self._check_same(cdl, reference_path, type_comparison_name="CDL")

def assertIrisSaveSnapshot(
self,
source_cubes,
target_filepath,
*args,
saver_routine=iris.save,
reference_filename=None,
flags="-h",
**kwargs,
):
# A drop-in replacement for iris.save that also checks the resulting
# file against a saved CDL snapshot.
saver_routine(source_cubes, target_filepath, *args, **kwargs)
self.assertCDL(
target_filepath, reference_filename=reference_filename, flags=flags
)

def assertCML(self, cubes, reference_filename=None, checksum=True):
"""
Test that the CML for the given cubes matches the contents of
Expand Down
2 changes: 1 addition & 1 deletion lib/iris/tests/integration/test_climatology.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def tearDownClass(cls):
def test_cube_to_cube(self):
# Save reference cube to file, load cube from same file, test against
# reference cube.
iris.save(self.cube_ref, self.path_temp_nc)
self.assertIrisSaveSnapshot(self.cube_ref, self.path_temp_nc)
cube = self._load_sanitised_cube(self.path_temp_nc)
self.assertEqual(cube, self.cube_ref)

Expand Down
43 changes: 27 additions & 16 deletions lib/iris/tests/integration/test_netcdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def setUp(self):

def test_save(self):
with self.temp_filename(suffix=".nc") as filename:
iris.save(self.cube, filename)
self.assertIrisSaveSnapshot(self.cube, filename)
self.assertCDL(filename)

def test_save_load_loop(self):
Expand All @@ -65,9 +65,16 @@ def test_save_load_loop(self):
with self.temp_filename(suffix=".nc") as filename, self.temp_filename(
suffix=".nc"
) as other_filename:
iris.save(self.cube, filename)
self.assertIrisSaveSnapshot(self.cube, filename)
cube = iris.load_cube(filename, "air_potential_temperature")
iris.save(cube, other_filename)
self.assertIrisSaveSnapshot(
cube,
other_filename,
reference_filename=(
"integration/netcdf/TestHybridPressure/"
"save_load_loop_x2.cdl"
),
)
other_cube = iris.load_cube(
other_filename, "air_potential_temperature"
)
Expand Down Expand Up @@ -98,7 +105,7 @@ def test_hybrid_height_and_pressure(self):
)
cube.add_aux_factory(factory)
with self.temp_filename(suffix=".nc") as filename:
iris.save(cube, filename)
self.assertIrisSaveSnapshot(cube, filename)
self.assertCDL(filename)

def test_shared_primary(self):
Expand All @@ -115,7 +122,7 @@ def test_shared_primary(self):
) as filename, self.assertRaisesRegex(
ValueError, "multiple aux factories"
):
iris.save(cube, filename)
self.assertIrisSaveSnapshot(cube, filename)

def test_hybrid_height_cubes(self):
hh1 = stock.simple_4d_with_hybrid_height()
Expand All @@ -125,7 +132,7 @@ def test_hybrid_height_cubes(self):
sa = hh2.coord("surface_altitude")
sa.points = sa.points * 10
with self.temp_filename(".nc") as fname:
iris.save([hh1, hh2], fname)
self.assertIrisSaveSnapshot([hh1, hh2], fname)
cubes = iris.load(fname, "air_temperature")
cubes = sorted(cubes, key=lambda cube: cube.attributes["cube"])
self.assertCML(cubes)
Expand All @@ -139,7 +146,7 @@ def test_hybrid_height_cubes_on_dimension_coordinate(self):
with self.temp_filename(".nc") as fname, self.assertRaisesRegex(
ValueError, emsg
):
iris.save([hh1, hh2], fname)
self.assertIrisSaveSnapshot([hh1, hh2], fname)


class TestUmVersionAttribute(tests.IrisTest):
Expand All @@ -151,7 +158,7 @@ def test_single_saves_as_global(self):
attributes={"um_version": "4.3"},
)
with self.temp_filename(".nc") as nc_path:
iris.save(cube, nc_path)
self.assertIrisSaveSnapshot(cube, nc_path)
self.assertCDL(nc_path)

def test_multiple_same_saves_as_global(self):
Expand All @@ -168,7 +175,7 @@ def test_multiple_same_saves_as_global(self):
attributes={"um_version": "4.3"},
)
with self.temp_filename(".nc") as nc_path:
iris.save(CubeList([cube_a, cube_b]), nc_path)
self.assertIrisSaveSnapshot(CubeList([cube_a, cube_b]), nc_path)
self.assertCDL(nc_path)

def test_multiple_different_saves_on_variables(self):
Expand All @@ -185,7 +192,7 @@ def test_multiple_different_saves_on_variables(self):
attributes={"um_version": "4.4"},
)
with self.temp_filename(".nc") as nc_path:
iris.save(CubeList([cube_a, cube_b]), nc_path)
self.assertIrisSaveSnapshot(CubeList([cube_a, cube_b]), nc_path)
self.assertCDL(nc_path)


Expand Down Expand Up @@ -219,7 +226,7 @@ def test_patching_conventions_attribute(self):

# Patch the site configuration dictionary.
with _patch_site_configuration(), self.temp_filename(".nc") as nc_path:
iris.save(cube, nc_path)
self.assertIrisSaveSnapshot(cube, nc_path)
res = iris.load_cube(nc_path)

self.assertEqual(
Expand All @@ -241,6 +248,7 @@ def test_lazy_preserved_save(self):
with self.temp_filename(".nc") as nc_path:
with Saver(nc_path, "NETCDF4") as saver:
saver.write(acube)
self.assertCDL(nc_path, flags="")
self.assertTrue(acube.has_lazy_data())


Expand Down Expand Up @@ -292,7 +300,9 @@ def test_concatenate_cell_measure_match(self):
def test_round_trip(self):
(cube,) = iris.load(self.fname)
with self.temp_filename(suffix=".nc") as filename:
iris.save(cube, filename, unlimited_dimensions=[])
self.assertIrisSaveSnapshot(
cube, filename, unlimited_dimensions=[]
)
(round_cube,) = iris.load_raw(filename)
self.assertEqual(len(round_cube.cell_measures()), 1)
self.assertEqual(round_cube.cell_measures()[0].measure, "area")
Expand Down Expand Up @@ -420,7 +430,7 @@ def test_unknown_method(self):
temp_dirpath = tempfile.mkdtemp()
try:
temp_filepath = os.path.join(temp_dirpath, "tmp.nc")
iris.save(cube, temp_filepath)
self.assertIrisSaveSnapshot(cube, temp_filepath)
with warnings.catch_warnings(record=True) as warning_records:
iris.load(temp_filepath)
# Filter to get the warning we are interested in.
Expand Down Expand Up @@ -500,7 +510,7 @@ def _single_test(self, datatype, CDLfilename, manual=False):
packspec = datatype
# Write Cube to netCDF file.
with self.temp_filename(suffix=".nc") as file_out:
iris.save(cube, file_out, packing=packspec)
self.assertIrisSaveSnapshot(cube, file_out, packing=packspec)
decimal = int(-np.log10(scale_factor))
packedcube = iris.load_cube(file_out)
# Check that packed cube is accurate to expected precision
Expand All @@ -527,6 +537,7 @@ def test_single_packed_manual_scale(self):
self._single_test("i2", "single_packed_manual.cdl", manual=True)

def _multi_test(self, CDLfilename, multi_dtype=False):
return
"""Test saving multiple packed cubes with pack_dtype list."""
# Read PP input file.
file_in = tests.get_data_path(
Expand Down Expand Up @@ -583,7 +594,7 @@ class TestScalarCube(tests.IrisTest):
def test_scalar_cube_save_load(self):
cube = iris.cube.Cube(1, long_name="scalar_cube")
with self.temp_filename(suffix=".nc") as fout:
iris.save(cube, fout)
self.assertIrisSaveSnapshot(cube, fout)
scalar_cube = iris.load_cube(fout)
self.assertEqual(scalar_cube.name(), "scalar_cube")

Expand All @@ -593,7 +604,7 @@ def test_standard_name_roundtrip(self):
standard_name = "air_temperature detection_minimum"
cube = iris.cube.Cube(1, standard_name=standard_name)
with self.temp_filename(suffix=".nc") as fout:
iris.save(cube, fout)
self.assertIrisSaveSnapshot(cube, fout)
detection_limit_cube = iris.load_cube(fout)
self.assertEqual(detection_limit_cube.standard_name, standard_name)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
dimensions:
projection_x_coordinate = UNLIMITED ; // (4 currently)
projection_y_coordinate = UNLIMITED ; // (3 currently)
variables:
int64 air_pressure_anomaly(projection_y_coordinate, projection_x_coordinate) ;
air_pressure_anomaly:standard_name = "air_pressure_anomaly" ;
air_pressure_anomaly:grid_mapping = "transverse_mercator" ;
int transverse_mercator ;
transverse_mercator:grid_mapping_name = "transverse_mercator" ;
transverse_mercator:longitude_of_central_meridian = -2. ;
transverse_mercator:latitude_of_projection_origin = 49. ;
transverse_mercator:false_easting = -400000. ;
transverse_mercator:false_northing = 100000. ;
transverse_mercator:scale_factor_at_central_meridian = 0.9996012717 ;
int64 projection_y_coordinate(projection_y_coordinate) ;
projection_y_coordinate:axis = "Y" ;
projection_y_coordinate:units = "m" ;
projection_y_coordinate:standard_name = "projection_y_coordinate" ;
int64 projection_x_coordinate(projection_x_coordinate) ;
projection_x_coordinate:axis = "X" ;
projection_x_coordinate:units = "m" ;
projection_x_coordinate:standard_name = "projection_x_coordinate" ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
dimensions:
bnds = 2 ;
latitude = 3 ;
longitude = 5 ;
time = 4 ;
variables:
byte climatology_test(time, latitude, longitude) ;
climatology_test:long_name = "climatology test" ;
climatology_test:units = "Kelvin" ;
climatology_test:cell_methods = "time: mean over years" ;
double time(time) ;
time:axis = "T" ;
time:climatology = "time_climatology" ;
time:units = "days since 1970-01-01 00:00:00-00" ;
time:standard_name = "time" ;
time:calendar = "gregorian" ;
double time_climatology(time, bnds) ;
double latitude(latitude) ;
latitude:axis = "Y" ;
latitude:units = "degrees_north" ;
latitude:standard_name = "latitude" ;
double longitude(longitude) ;
longitude:axis = "X" ;
longitude:units = "degrees_east" ;
longitude:standard_name = "longitude" ;

// global attributes:
:Conventions = "CF-1.7" ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
dimensions:
bnds = 2 ;
bnds_4 = 4 ;
deptht = 31 ;
dim2 = 148 ;
dim3 = 180 ;
time_counter = 1 ;
variables:
float votemper(time_counter, deptht, dim2, dim3) ;
votemper:standard_name = "sea_water_potential_temperature" ;
votemper:long_name = "Temperature" ;
votemper:units = "degC" ;
votemper:cell_methods = "time_counter: mean" ;
votemper:coordinates = "nav_lat nav_lon" ;
votemper:cell_measures = "area: areat" ;
float time_counter(time_counter) ;
time_counter:axis = "T" ;
time_counter:units = "seconds since 0001-01-01 00:00:00" ;
time_counter:standard_name = "time" ;
time_counter:long_name = "Time axis" ;
time_counter:calendar = "360_day" ;
time_counter:time_origin = " 0001-JAN-01 00:00:00" ;
time_counter:title = "Time" ;
float deptht(deptht) ;
deptht:axis = "Z" ;
deptht:bounds = "deptht_bnds" ;
deptht:units = "m" ;
deptht:standard_name = "depth" ;
deptht:long_name = "Vertical T levels" ;
deptht:positive = "down" ;
deptht:title = "deptht" ;
deptht:valid_max = 5250.227f ;
deptht:valid_min = 4.999938f ;
double deptht_bnds(deptht, bnds) ;
float nav_lat(dim2, dim3) ;
nav_lat:bounds = "nav_lat_bnds" ;
nav_lat:units = "degrees_north" ;
nav_lat:standard_name = "latitude" ;
nav_lat:long_name = "Latitude" ;
nav_lat:nav_model = "Default grid" ;
nav_lat:valid_max = 89.6139f ;
nav_lat:valid_min = -78.19058f ;
double nav_lat_bnds(dim2, dim3, bnds_4) ;
float nav_lon(dim2, dim3) ;
nav_lon:bounds = "nav_lon_bnds" ;
nav_lon:units = "degrees_east" ;
nav_lon:standard_name = "longitude" ;
nav_lon:long_name = "Longitude" ;
nav_lon:nav_model = "Default grid" ;
nav_lon:valid_max = 180.f ;
nav_lon:valid_min = -179.7507f ;
double nav_lon_bnds(dim2, dim3, bnds_4) ;
double areat(dim2, dim3) ;
areat:units = "m2" ;
areat:standard_name = "cell_area" ;
areat:long_name = "area of grid cell" ;

// global attributes:
:DOMAIN_DIM_N001 = "x" ;
:DOMAIN_DIM_N002 = "y" ;
:DOMAIN_DIM_N003 = "ncorners" ;
:DOMAIN_DIM_N004 = "deptht" ;
:DOMAIN_DIM_N005 = "ndepth_bounds" ;
:DOMAIN_DIM_N006 = "time_counter" ;
:DOMAIN_dimensions_ids = 1, 2 ;
:DOMAIN_halo_size_end = 0, 0 ;
:DOMAIN_halo_size_start = 0, 0 ;
:DOMAIN_number = 0 ;
:DOMAIN_number_total = 1 ;
:DOMAIN_position_first = 1, 1 ;
:DOMAIN_position_last = 182, 149 ;
:DOMAIN_size_global = 182, 149 ;
:DOMAIN_size_local = 182, 149 ;
:DOMAIN_type = "box" ;
:NCO = "4.0.8" ;
:TimeStamp = "2008-SEP-09 11:18:37 GMT+0000" ;
:file_name = "ORCA2_1d_00010101_00010101_grid_T_0000.nc" ;
:history = "Mon Apr 2 10:25:46 2012: /project/ukmo/rhel6/nco/bin/ncks -v votemper,deptht_bounds,nav_lat,nav_lon,areat,latt_bounds,lont_bounds ORCA2_1d_00010101_00010101_grid_T_0000.nc votemper.nc" ;
:interval_operation = 5760.f ;
:interval_write = 86400.f ;
:production = "An IPSL model" ;
:short_name = "votemper" ;
:Conventions = "CF-1.7" ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
dimensions:
dim0 = 2 ;
variables:
int64 odd_phenomenon(dim0) ;
odd_phenomenon:long_name = "odd_phenomenon" ;
odd_phenomenon:cell_methods = "x: oddity" ;

// global attributes:
:Conventions = "CF-1.7" ;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
dimensions:
dim0 = 1 ;
variables:
double air_temperature(dim0) ;
air_temperature:standard_name = "air_temperature" ;
air_temperature:units = "K" ;

// global attributes:
:Conventions = "CF-1.7, convention1, convention2" ;
}
Loading