From 30d1b16b81d48ccdca67afee98d8e8386acfb329 Mon Sep 17 00:00:00 2001 From: James McCreight Date: Thu, 13 Aug 2026 18:07:09 -0600 Subject: [PATCH] fix MetaData crash with packaging >= 26.3; declare packaging dependency MetaData.__filter_by_version relied on Version(None) raising TypeError to skip elements without version/deprecated XML attributes. packaging 26.3 raises InvalidVersion for None, escaping the handler and crashing MetaData() for any such element (63 in control.xml alone). Check the attributes for None explicitly instead of exception flow; behavior is unchanged on all packaging versions, including InvalidVersion still propagating for genuinely malformed version strings. Also declare the (already imported) packaging dependency in pyproject.toml and add a regression test. --- pyPRMS/metadata/metadata.py | 17 +++++++++-------- pyproject.toml | 1 + tests/func/test_MetaData.py | 13 +++++++++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/pyPRMS/metadata/metadata.py b/pyPRMS/metadata/metadata.py index e095115..50390f3 100644 --- a/pyPRMS/metadata/metadata.py +++ b/pyPRMS/metadata/metadata.py @@ -117,8 +117,12 @@ def __filter_by_version(self, elem: xmlET.Element, name: str, meta_dict[name] = {} - try: - var_version = Version(elem.attrib.get('version')) + # Missing version/deprecated attributes are checked explicitly: + # relying on Version(None) to raise is fragile (packaging < 26.3 + # raised TypeError, packaging >= 26.3 raises InvalidVersion). + version_attr = elem.attrib.get('version') + if version_attr is not None: + var_version = Version(version_attr) if var_version > req_version: if self.__verbose: # pragma: no cover @@ -126,11 +130,10 @@ def __filter_by_version(self, elem: xmlET.Element, name: str, del meta_dict[name] return True meta_dict[name]['version'] = str(var_version) - except TypeError: - pass - try: - depr_version = Version(elem.attrib.get('deprecated')) + depr_attr = elem.attrib.get('deprecated') + if depr_attr is not None: + depr_version = Version(depr_attr) if depr_version <= req_version: if self.__verbose: # pragma: no cover @@ -138,8 +141,6 @@ def __filter_by_version(self, elem: xmlET.Element, name: str, del meta_dict[name] return True meta_dict[name]['deprecated'] = depr_version - except TypeError: - pass return False diff --git a/pyproject.toml b/pyproject.toml index 1f665dd..682a984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = ["cartopy", "netCDF4!=1.7.4", "networkx>=2", "numpy", + "packaging", "pandas", "pyproj", "rich", diff --git a/tests/func/test_MetaData.py b/tests/func/test_MetaData.py index 5caaf7d..07316b7 100644 --- a/tests/func/test_MetaData.py +++ b/tests/func/test_MetaData.py @@ -26,3 +26,16 @@ def test_metadata_version5(self): def test_metadata_version4(self): prms_meta = MetaData(version='4', verbose=True).metadata assert prms_meta['parameters'].get('soilzone_aet_flag', None) is None + + def test_metadata_no_version_attributes(self): + """Entries lacking version/deprecated XML attributes are retained. + + Regression test: packaging >= 26.3 raises InvalidVersion instead of + TypeError for Version(None), which crashed MetaData for any element + without these attributes. + """ + prms_meta = MetaData(version='5.2.1.1', verbose=True).metadata + # cascade_flag has neither a version nor a deprecated attribute + assert 'cascade_flag' in prms_meta['control'] + # nhruOutON_OFF has only a deprecated attribute + assert 'nhruOutON_OFF' in prms_meta['control']