diff --git a/sofar/sofa.py b/sofar/sofa.py index 61f73ad..4723932 100644 --- a/sofar/sofa.py +++ b/sofar/sofa.py @@ -389,6 +389,54 @@ def inspect(self, file=None, issue_handling="print"): # output to console print(info_str) + @property + def convention_status(self): + """ + Get the status of the SOFA convention. + + Returns + ------- + status : str + The status of the SOFA convention + + - ``'current'`` if the convention is up to date. + - ``'deprecated'`` if the convention is outdated. In this case + :py:func:`~upgrade_convention` can be used to upgrade the data to + the latest version of the convention. + - ``'preliminary'`` if the convention is still under development + and not contained in the official SOFA standard, which is + indicated by a version number smaller than 1.0. Note that + preliminary conventions may be subject to change or could be + discarded completely. Data written with preliminary conventions + might thus become invalid in the future. + """ + + status = None + + # get deprecations and information about Sofa object + _, _, deprecations, upgrade = self._verification_rules() + convention = self.GLOBAL_SOFAConventions + version = self.GLOBAL_SOFAConventionsVersion + + # conventions can be completely deprecated or upgradable to a later + # version of the same convention or to a later convention + if convention in deprecations["GLOBAL:SOFAConventions"]: + status = 'deprecated' + elif convention in upgrade: + for from_to in upgrade[convention]["from_to"]: + if version in from_to[0]: + status = 'deprecated' + break + # conventions are preliminary if they are not deprecated and have a + # version number < 1.0 + if status is None and parse(version) < parse('1.0'): + status = 'preliminary' + # if both is not the case, the convention is current. + if status is None: + status = 'current' + + return status + def add_missing(self, mandatory=True, optional=True, verbose=True): """ Add missing data with default values. @@ -658,26 +706,17 @@ def upgrade_convention(self, target=None, verify='auto'): # check input --------------------------------------------------------- self._reset_convention() + status = self.convention_status # get deprecations and information about Sofa object - _, _, deprecations, upgrade = self._verification_rules() + _, _, _, upgrade = self._verification_rules() convention_current = self.GLOBAL_SOFAConventions version_current = self.GLOBAL_SOFAConventionsVersion sofa_version_current = self.GLOBAL_Version - # check if convention is deprecated ----------------------------------- - is_deprecated = False - - if convention_current in deprecations["GLOBAL:SOFAConventions"]: - is_deprecated = True - elif convention_current in upgrade: - for from_to in upgrade[convention_current]["from_to"]: - if version_current in from_to[0]: - is_deprecated = True - break - # check for upgrades -------------------------------------------------- - if is_deprecated: + if status == 'deprecated': + # check if upgrade is available for this convention if convention_current not in upgrade: print((f"Convention {convention_current} v{version_current} is" @@ -878,6 +917,7 @@ def verify(self, issue_handling="raise", mode="write"): # --------------------------------------------------------------------- # 0. update the convention self._reset_convention() + status = self.convention_status # --------------------------------------------------------------------- # 1. check if the mandatory attributes are contained @@ -1288,21 +1328,18 @@ def verify(self, issue_handling="raise", mode="write"): # --------------------------------------------------------------------- # 8. check deprecations # (so far there are only deprecations for the convention) - if self.GLOBAL_SOFAConventions in \ - deprecations["GLOBAL:SOFAConventions"]: - convention = self.GLOBAL_SOFAConventions + if status == 'deprecated': msg = ("Detected deprecations:\n" f"- GLOBAL_SOFAConventions is " - f"{self.GLOBAL_SOFAConventions}, which is deprecated. Use " - "Sofa.upgrade_convention() to upgrade to " - f"{deprecations['GLOBAL:SOFAConventions'][convention]}") + f"{self.GLOBAL_SOFAConventions}, which is deprecated. See " + "Sofa.upgrade_convention() for upgrade possibilities.") if mode == "write": error_msg += msg else: warning_msg += msg # warn if preliminary conventions versions are used - if float(self.GLOBAL_SOFAConventionsVersion) < 1.0: + if status == 'preliminary': warning_msg += ( "\n\nDetected preliminary conventions version " f"{self.GLOBAL_SOFAConventionsVersion}:\n - Upgrade data to " diff --git a/tests/test_io.py b/tests/test_io.py index 6186b4d..f8e93c4 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -12,7 +12,6 @@ import numpy as np import numpy.testing as npt from netCDF4 import Dataset -from packaging.version import parse def test_read_write_sofa(): @@ -166,18 +165,21 @@ def test_roundtrip(mandatory): for name, version in names_versions: print(f"Testing: {name} {version}") - # writing deprecated and proposed conventions is not tested - if name in deprecations["GLOBAL:SOFAConventions"] or \ - parse(version) < parse('1.0'): - sofa = sf.Sofa(name, mandatory, version, verify=False) - # non stable conventions are not verified - if parse(version) >= parse('1.0'): - with pytest.warns(UserWarning, match="deprecations"): - sofa.verify(mode="read") - else: + # create Sofa object without verification. Verification will be done + # when writing below. + sofa = sf.Sofa(name, mandatory, version, verify=False) + status = sofa.convention_status + + if status == 'preliminary': + # don't test anything for preliminary conventions + return + elif status == 'deprecated': + # don't test writing deprecated conventions + with pytest.warns(UserWarning, match="deprecations"): + sofa.verify(mode="read") + elif status == 'current': # test full round-trip for other conventions file = os.path.join(temp_dir.name, name + ".sofa") - sofa = sf.Sofa(name, mandatory, version) sf.write_sofa(file, sofa) sofa_r = sf.read_sofa(file) identical = sf.equals(sofa, sofa_r, verbose=True, exclude="DATE") diff --git a/tests/test_sofa.py b/tests/test_sofa.py index 9e9810e..a947938 100644 --- a/tests/test_sofa.py +++ b/tests/test_sofa.py @@ -169,6 +169,26 @@ def test_inspect(capfd): assert out == "".join(text) +@pytest.mark.parametrize(('sofa', 'status'), [ + # up to date convention + (sf.Sofa('FreeFieldDirectivityTF'), + 'current'), + # convention with outdated version + (sf.Sofa('FreeFieldDirectivityTF', version='1.0', verify=False), + 'deprecated'), + # deprecated convention + (sf.Sofa('GeneralFIRE', verify=False), + 'deprecated'), + # preliminary convention. NOTE: This test will fail if the convention + # becomes standardized. In this case the status will change to 'deprecated' + (sf.Sofa('AnnotatedEmitterAudio', version='0.2', verify=False), + 'preliminary'), +]) +def test_deprecated(sofa, status): + + assert sofa.convention_status == status + + def test_add_entry(): sofa = sf.Sofa("GeneralTF")