Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 57 additions & 20 deletions sofar/sofa.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]}")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the error message failed in case a conventio is deprecated but a later version of the same convnetion is available. Simplified the error message to work in all cases

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 "
Expand Down
24 changes: 13 additions & 11 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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")
Expand Down
20 changes: 20 additions & 0 deletions tests/test_sofa.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down