Skip to content
33 changes: 25 additions & 8 deletions iris_grib/_load_convert.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2014 - 2018, Met Office
# (C) British Crown Copyright 2014 - 2019, Met Office
#
# This file is part of iris-grib.
#
Expand Down Expand Up @@ -2354,6 +2354,7 @@ def product_definition_section(section, metadata, discipline, tablesVersion,
template = section['productDefinitionTemplateNumber']

probability = None
includes_fixed_surface_keys = True
if template == 0:
# Process analysis or forecast at a horizontal level or
# in a horizontal layer at a point in time.
Expand All @@ -2377,8 +2378,10 @@ def product_definition_section(section, metadata, discipline, tablesVersion,
product_definition_template_15(section, metadata, rt_coord)
elif template == 31:
# Process satellite product.
includes_fixed_surface_keys = False
product_definition_template_31(section, metadata, rt_coord)
elif template == 32:
includes_fixed_surface_keys = False
product_definition_template_32(section, metadata, rt_coord)
elif template == 40:
product_definition_template_40(section, metadata, rt_coord)
Expand All @@ -2389,13 +2392,27 @@ def product_definition_section(section, metadata, discipline, tablesVersion,

# Translate GRIB2 phenomenon to CF phenomenon.
if tablesVersion != _CODE_TABLES_MISSING:
translate_phenomenon(metadata, discipline,
section['parameterCategory'],
section['parameterNumber'],
section['typeOfFirstFixedSurface'],
section['scaledValueOfFirstFixedSurface'],
section['typeOfSecondFixedSurface'],
probability=probability)
translation_kwargs = {
'metadata': metadata,
'discipline': discipline,
'parameterCategory': section['parameterCategory'],
'parameterNumber': section['parameterNumber'],
'probability': probability
}

# Won't always be able to populate the below arguments -
# missing from some template definitions.
fixed_surface_keys = [
'typeOfFirstFixedSurface',
'scaledValueOfFirstFixedSurface',
'typeOfSecondFixedSurface'
]

for section_key in fixed_surface_keys:
translation_kwargs[section_key] = \
section[section_key] if includes_fixed_surface_keys else None

translate_phenomenon(**translation_kwargs)


###############################################################################
Expand Down
138 changes: 138 additions & 0 deletions iris_grib/tests/unit/load_convert/test_product_definition_section.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# (C) British Crown Copyright 2014 - 2019, Met Office
#
# This file is part of iris-grib.
#
# iris-grib is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# iris-grib is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with iris-grib. If not, see <http://www.gnu.org/licenses/>.
"""
Tests for `iris_grib._load_convert.product_definition_section`.

"""

from __future__ import (absolute_import, division, print_function)

# import iris_grib.tests first so that some things can be initialised
# before importing anything else.
import iris_grib.tests as tests

from iris.coords import DimCoord
import mock
import six

from itertools import product

from iris_grib._load_convert import product_definition_section
from iris_grib.tests.unit.load_convert import empty_metadata
from iris_grib.tests.unit.load_convert.test_product_definition_template_0 \
import section_4 as pdt_0_section_4
from iris_grib.tests.unit.load_convert.test_product_definition_template_31 \
import section_4 as pdt_31_section_4


class TestFixedSurfaces(tests.IrisGribTest):
"""
Tests focussing on the handling of fixed surface elements in section 4.
Expects/ignores depending on the template number.
"""
def setUp(self):
self.patch('warnings.warn')
self.translate_phenomenon_patch = self.patch(
'iris_grib._load_convert.translate_phenomenon'
)

# Prep placeholder variables for product_definition_section.
self.discipline = mock.sentinel.discipline
self.tablesVersion = mock.sentinel.tablesVersion
self.rt_coord = DimCoord(24, 'forecast_reference_time',
units='hours since epoch')
self.metadata = empty_metadata()

self.templates = {0: pdt_0_section_4(), 31: pdt_31_section_4()}
self.fixed_surface_keys = [
'typeOfFirstFixedSurface',
'scaledValueOfFirstFixedSurface',
'typeOfSecondFixedSurface'
]

def _check_fixed_surface(self, fs_is_expected, fs_is_present):
"""
Whether or not fixed surface elements are expected/present in the
section 4 keys, most of the code is shared so we are using a single
function with parameters.
"""

# Use the section 4 from either product_definition_section #1 or #31.
# #0 contains fixed surface elements, #31 does not.
template_number = 0 if fs_is_expected else 31
section_4 = self.templates[template_number]
section_4.update({
'productDefinitionTemplateNumber': template_number,
'parameterCategory': None,
'parameterNumber': None
})

for key in self.fixed_surface_keys:
# Force the presence or absence of the fixed surface elements even
# when they're respectively ignored or expected.
if fs_is_present and key not in section_4:
section_4[key] = pdt_0_section_4()[key]
elif (not fs_is_present) and key in section_4:
del section_4[key]

def run_function():
# For re-use in every type of test below.
product_definition_section(
section_4, self.metadata, self.discipline, self.tablesVersion,
self.rt_coord)

if fs_is_expected and not fs_is_present:
# Should error since the expected keys are missing.
error_message = 'FixedSurface'
with six.assertRaisesRegex(self, KeyError, error_message):
run_function()
else:
# Should have a successful run for all other circumstances.

# Translate_phenomenon_patch is the end of the function,
# and should be able to accept None for the fixed surface
# arguments. So should always have run.
previous_call_count = self.translate_phenomenon_patch.call_count
run_function()
self.assertEqual(self.translate_phenomenon_patch.call_count,
previous_call_count + 1)
phenom_call_args = self.translate_phenomenon_patch.call_args[1]
for key in self.fixed_surface_keys:
# Check whether None or actual values have been passed for
# the fixed surface arguments.
if fs_is_expected:
self.assertEqual(phenom_call_args[key], section_4[key])
else:
self.assertIsNone(phenom_call_args[key])

def test_all_combinations(self):
"""
Test all combinations of fixed surface being expected/present

a. Expected and Present - standard behaviour for most templates
b. Expected and Absent - unplanned combination, should error
c. Unexpected and Present - unplanned combination, should be handled
identically to (d)
d. Unexpected and Absent - standard behaviour for a few templates
e.g. #31
"""
for pair in product([True, False], repeat=2):
self._check_fixed_surface(*pair)


if __name__ == '__main__':
tests.main()
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# (C) British Crown Copyright 2014 - 2017, Met Office
# (C) British Crown Copyright 2014 - 2019, Met Office
#
# This file is part of iris-grib.
#
Expand Down Expand Up @@ -37,6 +37,19 @@
from iris_grib._load_convert import product_definition_template_31


def section_4():
# Also needed for test_product_definition_section.py.
series = mock.sentinel.satelliteSeries
number = mock.sentinel.satelliteNumber
instrument = mock.sentinel.instrumentType
return {'NB': 1,
'satelliteSeries': series,
'satelliteNumber': number,
'instrumentType': instrument,
'scaleFactorOfCentralWaveNumber': 1,
'scaledValueOfCentralWaveNumber': 12}


class Test(tests.IrisGribTest):
def setUp(self):
self.patch('warnings.warn')
Expand All @@ -47,16 +60,8 @@ def setUp(self):

def test(self):
# Prepare the arguments.
series = mock.sentinel.satelliteSeries
number = mock.sentinel.satelliteNumber
instrument = mock.sentinel.instrumentType
rt_coord = mock.sentinel.observation_time
section = {'NB': 1,
'satelliteSeries': series,
'satelliteNumber': number,
'instrumentType': instrument,
'scaleFactorOfCentralWaveNumber': 1,
'scaledValueOfCentralWaveNumber': 12}
section = section_4()

# Call the function.
metadata = empty_metadata()
Expand Down