Skip to content

Commit 00a72eb

Browse files
trexfeathersbjlittle
authored andcommitted
Granularity for fixed surface arguments (#146)
product_definition_section will no longer try to populate the fixed surface arguments for translate_phenomenon - not available in some templates, in which case populate with None
1 parent bc2dd95 commit 00a72eb

File tree

3 files changed

+178
-18
lines changed

3 files changed

+178
-18
lines changed

iris_grib/_load_convert.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2014 - 2018, Met Office
1+
# (C) British Crown Copyright 2014 - 2019, Met Office
22
#
33
# This file is part of iris-grib.
44
#
@@ -2354,6 +2354,7 @@ def product_definition_section(section, metadata, discipline, tablesVersion,
23542354
template = section['productDefinitionTemplateNumber']
23552355

23562356
probability = None
2357+
includes_fixed_surface_keys = True
23572358
if template == 0:
23582359
# Process analysis or forecast at a horizontal level or
23592360
# in a horizontal layer at a point in time.
@@ -2377,8 +2378,10 @@ def product_definition_section(section, metadata, discipline, tablesVersion,
23772378
product_definition_template_15(section, metadata, rt_coord)
23782379
elif template == 31:
23792380
# Process satellite product.
2381+
includes_fixed_surface_keys = False
23802382
product_definition_template_31(section, metadata, rt_coord)
23812383
elif template == 32:
2384+
includes_fixed_surface_keys = False
23822385
product_definition_template_32(section, metadata, rt_coord)
23832386
elif template == 40:
23842387
product_definition_template_40(section, metadata, rt_coord)
@@ -2389,13 +2392,27 @@ def product_definition_section(section, metadata, discipline, tablesVersion,
23892392

23902393
# Translate GRIB2 phenomenon to CF phenomenon.
23912394
if tablesVersion != _CODE_TABLES_MISSING:
2392-
translate_phenomenon(metadata, discipline,
2393-
section['parameterCategory'],
2394-
section['parameterNumber'],
2395-
section['typeOfFirstFixedSurface'],
2396-
section['scaledValueOfFirstFixedSurface'],
2397-
section['typeOfSecondFixedSurface'],
2398-
probability=probability)
2395+
translation_kwargs = {
2396+
'metadata': metadata,
2397+
'discipline': discipline,
2398+
'parameterCategory': section['parameterCategory'],
2399+
'parameterNumber': section['parameterNumber'],
2400+
'probability': probability
2401+
}
2402+
2403+
# Won't always be able to populate the below arguments -
2404+
# missing from some template definitions.
2405+
fixed_surface_keys = [
2406+
'typeOfFirstFixedSurface',
2407+
'scaledValueOfFirstFixedSurface',
2408+
'typeOfSecondFixedSurface'
2409+
]
2410+
2411+
for section_key in fixed_surface_keys:
2412+
translation_kwargs[section_key] = \
2413+
section[section_key] if includes_fixed_surface_keys else None
2414+
2415+
translate_phenomenon(**translation_kwargs)
23992416

24002417

24012418
###############################################################################
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# (C) British Crown Copyright 2014 - 2019, Met Office
2+
#
3+
# This file is part of iris-grib.
4+
#
5+
# iris-grib is free software: you can redistribute it and/or modify it under
6+
# the terms of the GNU Lesser General Public License as published by the
7+
# Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# iris-grib is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public License
16+
# along with iris-grib. If not, see <http://www.gnu.org/licenses/>.
17+
"""
18+
Tests for `iris_grib._load_convert.product_definition_section`.
19+
20+
"""
21+
22+
from __future__ import (absolute_import, division, print_function)
23+
24+
# import iris_grib.tests first so that some things can be initialised
25+
# before importing anything else.
26+
import iris_grib.tests as tests
27+
28+
from iris.coords import DimCoord
29+
import mock
30+
import six
31+
32+
from itertools import product
33+
34+
from iris_grib._load_convert import product_definition_section
35+
from iris_grib.tests.unit.load_convert import empty_metadata
36+
from iris_grib.tests.unit.load_convert.test_product_definition_template_0 \
37+
import section_4 as pdt_0_section_4
38+
from iris_grib.tests.unit.load_convert.test_product_definition_template_31 \
39+
import section_4 as pdt_31_section_4
40+
41+
42+
class TestFixedSurfaces(tests.IrisGribTest):
43+
"""
44+
Tests focussing on the handling of fixed surface elements in section 4.
45+
Expects/ignores depending on the template number.
46+
"""
47+
def setUp(self):
48+
self.patch('warnings.warn')
49+
self.translate_phenomenon_patch = self.patch(
50+
'iris_grib._load_convert.translate_phenomenon'
51+
)
52+
53+
# Prep placeholder variables for product_definition_section.
54+
self.discipline = mock.sentinel.discipline
55+
self.tablesVersion = mock.sentinel.tablesVersion
56+
self.rt_coord = DimCoord(24, 'forecast_reference_time',
57+
units='hours since epoch')
58+
self.metadata = empty_metadata()
59+
60+
self.templates = {0: pdt_0_section_4(), 31: pdt_31_section_4()}
61+
self.fixed_surface_keys = [
62+
'typeOfFirstFixedSurface',
63+
'scaledValueOfFirstFixedSurface',
64+
'typeOfSecondFixedSurface'
65+
]
66+
67+
def _check_fixed_surface(self, fs_is_expected, fs_is_present):
68+
"""
69+
Whether or not fixed surface elements are expected/present in the
70+
section 4 keys, most of the code is shared so we are using a single
71+
function with parameters.
72+
"""
73+
74+
# Use the section 4 from either product_definition_section #1 or #31.
75+
# #0 contains fixed surface elements, #31 does not.
76+
template_number = 0 if fs_is_expected else 31
77+
section_4 = self.templates[template_number]
78+
section_4.update({
79+
'productDefinitionTemplateNumber': template_number,
80+
'parameterCategory': None,
81+
'parameterNumber': None
82+
})
83+
84+
for key in self.fixed_surface_keys:
85+
# Force the presence or absence of the fixed surface elements even
86+
# when they're respectively ignored or expected.
87+
if fs_is_present and key not in section_4:
88+
section_4[key] = pdt_0_section_4()[key]
89+
elif (not fs_is_present) and key in section_4:
90+
del section_4[key]
91+
92+
def run_function():
93+
# For re-use in every type of test below.
94+
product_definition_section(
95+
section_4, self.metadata, self.discipline, self.tablesVersion,
96+
self.rt_coord)
97+
98+
if fs_is_expected and not fs_is_present:
99+
# Should error since the expected keys are missing.
100+
error_message = 'FixedSurface'
101+
with six.assertRaisesRegex(self, KeyError, error_message):
102+
run_function()
103+
else:
104+
# Should have a successful run for all other circumstances.
105+
106+
# Translate_phenomenon_patch is the end of the function,
107+
# and should be able to accept None for the fixed surface
108+
# arguments. So should always have run.
109+
previous_call_count = self.translate_phenomenon_patch.call_count
110+
run_function()
111+
self.assertEqual(self.translate_phenomenon_patch.call_count,
112+
previous_call_count + 1)
113+
phenom_call_args = self.translate_phenomenon_patch.call_args[1]
114+
for key in self.fixed_surface_keys:
115+
# Check whether None or actual values have been passed for
116+
# the fixed surface arguments.
117+
if fs_is_expected:
118+
self.assertEqual(phenom_call_args[key], section_4[key])
119+
else:
120+
self.assertIsNone(phenom_call_args[key])
121+
122+
def test_all_combinations(self):
123+
"""
124+
Test all combinations of fixed surface being expected/present
125+
126+
a. Expected and Present - standard behaviour for most templates
127+
b. Expected and Absent - unplanned combination, should error
128+
c. Unexpected and Present - unplanned combination, should be handled
129+
identically to (d)
130+
d. Unexpected and Absent - standard behaviour for a few templates
131+
e.g. #31
132+
"""
133+
for pair in product([True, False], repeat=2):
134+
self._check_fixed_surface(*pair)
135+
136+
137+
if __name__ == '__main__':
138+
tests.main()

iris_grib/tests/unit/load_convert/test_product_definition_template_31.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2014 - 2017, Met Office
1+
# (C) British Crown Copyright 2014 - 2019, Met Office
22
#
33
# This file is part of iris-grib.
44
#
@@ -37,6 +37,19 @@
3737
from iris_grib._load_convert import product_definition_template_31
3838

3939

40+
def section_4():
41+
# Also needed for test_product_definition_section.py.
42+
series = mock.sentinel.satelliteSeries
43+
number = mock.sentinel.satelliteNumber
44+
instrument = mock.sentinel.instrumentType
45+
return {'NB': 1,
46+
'satelliteSeries': series,
47+
'satelliteNumber': number,
48+
'instrumentType': instrument,
49+
'scaleFactorOfCentralWaveNumber': 1,
50+
'scaledValueOfCentralWaveNumber': 12}
51+
52+
4053
class Test(tests.IrisGribTest):
4154
def setUp(self):
4255
self.patch('warnings.warn')
@@ -47,16 +60,8 @@ def setUp(self):
4760

4861
def test(self):
4962
# Prepare the arguments.
50-
series = mock.sentinel.satelliteSeries
51-
number = mock.sentinel.satelliteNumber
52-
instrument = mock.sentinel.instrumentType
5363
rt_coord = mock.sentinel.observation_time
54-
section = {'NB': 1,
55-
'satelliteSeries': series,
56-
'satelliteNumber': number,
57-
'instrumentType': instrument,
58-
'scaleFactorOfCentralWaveNumber': 1,
59-
'scaledValueOfCentralWaveNumber': 12}
64+
section = section_4()
6065

6166
# Call the function.
6267
metadata = empty_metadata()

0 commit comments

Comments
 (0)