Skip to content

Commit 8887c69

Browse files
committed
Implement capturing for cell methods.
1 parent 5685f82 commit 8887c69

File tree

3 files changed

+120
-4
lines changed

3 files changed

+120
-4
lines changed

lib/iris/fileformats/_nc_load_rules/actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def action_default(engine):
109109
"""Perform standard operations for every cube."""
110110
# Future pattern (iris#6319).
111111
hh.build_and_add_names(engine)
112+
hh.build_and_add_cell_methods(engine)
112113

113114
# Legacy pattern.
114115
hh.build_cube_metadata(engine)

lib/iris/fileformats/_nc_load_rules/helpers.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,29 @@ def setter(attr_name):
650650
)
651651

652652

653+
################################################################################
654+
def _build_cell_methods(cf_var: cf.CFDataVariable) -> List[iris.coords.CellMethod]:
655+
nc_att_cell_methods = getattr(cf_var, CF_ATTR_CELL_METHODS, None)
656+
return parse_cell_methods(nc_att_cell_methods, cf_var.cf_name)
657+
658+
659+
def build_and_add_cell_methods(engine: Engine):
660+
"""Add cell methods to the cube."""
661+
assert engine.cf_var is not None
662+
assert engine.cube is not None
663+
664+
_add_or_capture(
665+
build_func=partial(_build_cell_methods, engine.cf_var),
666+
add_method=partial(setattr, engine.cube, "cell_methods"),
667+
cf_var=engine.cf_var,
668+
attr_key=CF_ATTR_CELL_METHODS,
669+
destination=LoadProblems.Problem.Destination(
670+
iris_class=Cube,
671+
identifier=engine.cf_var.cf_name,
672+
),
673+
)
674+
675+
653676
################################################################################
654677
def build_cube_metadata(engine):
655678
"""Add the standard meta data to the cube."""
@@ -664,10 +687,6 @@ def build_cube_metadata(engine):
664687
attr_units = get_attr_units(cf_var, cube.attributes)
665688
cube.units = attr_units
666689

667-
# Incorporate cell methods
668-
nc_att_cell_methods = getattr(cf_var, CF_ATTR_CELL_METHODS, None)
669-
cube.cell_methods = parse_cell_methods(nc_att_cell_methods, cf_var.cf_name)
670-
671690
# Set the cube global attributes.
672691
for attr_name, attr_value in cf_var.cf_group.global_attributes.items():
673692
try:
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright Iris contributors
2+
#
3+
# This file is part of Iris and is released under the BSD license.
4+
# See LICENSE in the root of the repository for full licensing details.
5+
"""Test function :func:`iris.fileformats._nc_load_rules.helpers.build_and_add_cell_methods`."""
6+
7+
from unittest import mock
8+
9+
import pytest
10+
11+
from iris.coords import CellMethod
12+
from iris.cube import Cube
13+
from iris.fileformats._nc_load_rules import helpers
14+
from iris.fileformats.netcdf import _thread_safe_nc as threadsafe_nc
15+
from iris.loading import LOAD_PROBLEMS
16+
17+
18+
@pytest.fixture
19+
def mock_cf_data_var():
20+
yield mock.Mock(
21+
spec=threadsafe_nc.VariableWrapper,
22+
cell_methods="time: mean",
23+
cf_name="wibble",
24+
filename="DUMMY",
25+
)
26+
27+
28+
@pytest.fixture
29+
def mock_engine(mock_cf_data_var):
30+
yield mock.Mock(
31+
cube=mock.Mock(),
32+
cf_var=mock_cf_data_var,
33+
filename=mock_cf_data_var.filename,
34+
)
35+
36+
37+
def test_construction(mock_engine):
38+
expected_method = CellMethod("mean", coords=["time"])
39+
helpers.build_and_add_cell_methods(mock_engine)
40+
assert mock_engine.cube.cell_methods == (expected_method,)
41+
42+
43+
def test_not_built(monkeypatch, mock_engine, mock_cf_data_var):
44+
cm_original = mock_engine.cube.cell_methods
45+
46+
def mock_parse_cell_methods(nc_cell_methods, cf_name=None):
47+
raise RuntimeError("Not built")
48+
49+
with monkeypatch.context() as m:
50+
m.setattr(helpers, "parse_cell_methods", mock_parse_cell_methods)
51+
helpers.build_and_add_cell_methods(mock_engine)
52+
53+
load_problem = LOAD_PROBLEMS.problems[-1]
54+
assert "Not built" in "".join(load_problem.stack_trace.format())
55+
assert mock_engine.cube.cell_methods == cm_original
56+
57+
58+
def test_not_added(monkeypatch, mock_engine, mock_cf_data_var):
59+
cm_original = mock_engine.cube.cell_methods
60+
61+
class NoCellMethods(mock.Mock):
62+
def __setattr__(self, key, value):
63+
if key == "cell_methods":
64+
raise RuntimeError("Not added")
65+
super().__setattr__(key, value)
66+
67+
with monkeypatch.context() as m:
68+
m.setattr(mock_engine, "cube", NoCellMethods())
69+
helpers.build_and_add_cell_methods(mock_engine)
70+
71+
load_problem = LOAD_PROBLEMS.problems[-1]
72+
assert "Not added" in "".join(load_problem.stack_trace.format())
73+
assert mock_engine.cube.cell_methods == cm_original
74+
75+
76+
def test_unhandlable_error(monkeypatch, mock_engine):
77+
# Confirm that the code can redirect an error to LOAD_PROBLEMS even
78+
# when there is no specific handling code for it.
79+
n_problems = len(LOAD_PROBLEMS.problems)
80+
81+
with monkeypatch.context() as m:
82+
m.setattr(mock_engine, "cube", "foo")
83+
helpers.build_and_add_cell_methods(mock_engine)
84+
85+
assert len(LOAD_PROBLEMS.problems) > n_problems
86+
87+
88+
def test_problem_destination(monkeypatch, mock_engine):
89+
# Confirm that the destination of the problem is set correctly.
90+
with monkeypatch.context() as m:
91+
m.setattr(mock_engine, "cube", "foo")
92+
helpers.build_and_add_cell_methods(mock_engine)
93+
94+
destination = LOAD_PROBLEMS.problems[-1].destination
95+
assert destination.iris_class is Cube
96+
assert destination.identifier == mock_engine.cf_var.cf_name

0 commit comments

Comments
 (0)