Skip to content

Commit 466b7d4

Browse files
Enable callbacks for loading netcdf with multiple variables (#6754)
* enable callbacks for loading netcdf with multiple variables * ruff format * update docstring * add integration test * address review comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * address review comment --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9c06cca commit 466b7d4

File tree

4 files changed

+63
-17
lines changed

4 files changed

+63
-17
lines changed

docs/src/whatsnew/latest.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ This document explains the changes made to Iris for this release
9191
is hoped that a future ``libnetcdf`` release will recover the original
9292
performance. See `netcdf-c#3183`_ for more details. (:pull:`6747`)
9393

94+
#. `@stephenworsley`_ made NetCDF loading more efficient by filtering variables
95+
before they become instantiated as cubes in the case where multiple name
96+
constraints are given. This was previously only implemented where one such
97+
constraint was given. (:issue:`6228`, :pull:`6754`)
98+
9499

95100
🔥 Deprecations
96101
===============

lib/iris/fileformats/netcdf/loader.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -601,24 +601,26 @@ def _translate_constraints_to_var_callback(constraints):
601601
602602
Notes
603603
-----
604-
For now, ONLY handles a single NameConstraint with no 'STASH' component.
604+
For now, ONLY handles NameConstraints with no 'STASH' component.
605605
606606
"""
607607
import iris._constraints
608608

609609
constraints = iris._constraints.list_of_constraints(constraints)
610-
result = None
611-
if len(constraints) == 1:
612-
(constraint,) = constraints
613-
if (
614-
isinstance(constraint, iris._constraints.NameConstraint)
615-
and constraint.STASH == "none"
616-
):
617-
# As long as it doesn't use a STASH match, then we can treat it as
618-
# a testing against name properties of cf_var.
619-
# That's just like testing against name properties of a cube, except that they may not all exist.
620-
def inner(cf_datavar):
621-
match = True
610+
if len(constraints) == 0 or not all(
611+
isinstance(constraint, iris._constraints.NameConstraint)
612+
and constraint.STASH == "none"
613+
for constraint in constraints
614+
):
615+
# We can define a var-filtering function to speedup the load, *ONLY* when we
616+
# have some constraints, and all are simple NameConstraints with no STASH.
617+
result = None
618+
else:
619+
620+
def inner(cf_datavar):
621+
match_any_constraint = False
622+
for constraint in constraints:
623+
match_this_constraint = True
622624
for name in constraint._names:
623625
expected = getattr(constraint, name)
624626
if name != "STASH" and expected != "none":
@@ -629,11 +631,14 @@ def inner(cf_datavar):
629631
continue
630632
actual = getattr(cf_datavar, attr_name, "")
631633
if actual != expected:
632-
match = False
634+
match_this_constraint = False
633635
break
634-
return match
636+
if match_this_constraint:
637+
match_any_constraint = True
638+
break
639+
return match_any_constraint
635640

636-
result = inner
641+
result = inner
637642
return result
638643

639644

lib/iris/tests/integration/netcdf/test_general.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,13 @@ def test_netcdf_with_NameConstraint(self):
314314
self.assertEqual(len(cubes), 1)
315315
self.assertEqual(cubes[0].var_name, "cdf_temp_dmax_tmean_abs")
316316

317+
def test_netcdf_with_2_NameConstraints(self):
318+
var_names = ["cdf_temp_dmax_tmean_abs", "temp_dmax_tmean_abs"]
319+
constrs = [iris.NameConstraint(var_name=var_name) for var_name in var_names]
320+
cubes = iris.load(self.filename, constrs)
321+
self.assertEqual(len(cubes), 2)
322+
self.assertEqual(sorted([cube.var_name for cube in cubes]), var_names)
323+
317324
def test_netcdf_with_no_constraint(self):
318325
cubes = iris.load(self.filename)
319326
self.assertEqual(len(cubes), 3)

lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,35 @@ class Test(tests.IrisTest):
3333
def test_multiple_constraints(self):
3434
constrs = [
3535
iris.NameConstraint(standard_name="x_wind"),
36-
iris.NameConstraint(var_name="var1"),
36+
iris.NameConstraint(var_name="var2"),
37+
]
38+
callback = _translate_constraints_to_var_callback(constrs)
39+
result = [callback(var) for var in self.data_variables]
40+
self.assertArrayEqual(result, [True, True, False, True, False])
41+
42+
def test_multiple_constraints_invalid(self):
43+
constrs = [
44+
iris.NameConstraint(standard_name="x_wind"),
45+
iris.NameConstraint(var_name="var1", STASH="m01s00i024"),
3746
]
3847
result = _translate_constraints_to_var_callback(constrs)
3948
self.assertIsNone(result)
4049

50+
def test_multiple_constraints__multiname(self):
51+
# Modify the first constraint to require BOTH var-name and std-name match
52+
constrs = [
53+
iris.NameConstraint(standard_name="x_wind", var_name="var1"),
54+
iris.NameConstraint(var_name="var2"),
55+
]
56+
callback = _translate_constraints_to_var_callback(constrs)
57+
# Add 2 extra vars: one passes both name checks, and the other does not
58+
vars = self.data_variables + [
59+
CFDataVariable("var1", MagicMock(standard_name="x_wind")),
60+
CFDataVariable("var1", MagicMock(standard_name="air_pressure")),
61+
]
62+
result = [callback(var) for var in vars]
63+
self.assertArrayEqual(result, [True, True, False, True, False, True, False])
64+
4165
def test_non_NameConstraint(self):
4266
constr = iris.AttributeConstraint(STASH="m01s00i002")
4367
result = _translate_constraints_to_var_callback(constr)
@@ -91,6 +115,11 @@ def test_NameConstraint_with_STASH(self):
91115
result = _translate_constraints_to_var_callback(constr)
92116
self.assertIsNone(result)
93117

118+
def test_no_constraints(self):
119+
constrs = []
120+
result = _translate_constraints_to_var_callback(constrs)
121+
self.assertIsNone(result)
122+
94123

95124
if __name__ == "__main__":
96125
tests.main()

0 commit comments

Comments
 (0)