From f3503fd3a4ccecb015899f871078759df6d703f7 Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Mon, 19 Jan 2026 18:01:02 +0000 Subject: [PATCH 1/5] Fix creation of classmethod test fixtures Python 3.13 classmethods behave differently, so the order of decorators needs to change --- gwsumm/tests/test_config.py | 2 +- gwsumm/tests/test_plot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gwsumm/tests/test_config.py b/gwsumm/tests/test_config.py index 7d19ba82..d7d442f9 100644 --- a/gwsumm/tests/test_config.py +++ b/gwsumm/tests/test_config.py @@ -73,8 +73,8 @@ def new(cls): TEST_CONFIG.seek(0) return cp + @pytest.fixture @classmethod - @pytest.fixture() def cnfg(cls): return cls.new() diff --git a/gwsumm/tests/test_plot.py b/gwsumm/tests/test_plot.py index e59a469d..a5099f60 100644 --- a/gwsumm/tests/test_plot.py +++ b/gwsumm/tests/test_plot.py @@ -103,8 +103,8 @@ def create(cls, *args, **kwargs): return cls.PLOT(*args, **kwargs) return cls.PLOT(*cls.DEFAULT_ARGS, **cls.DEFAULT_KWARGS) + @pytest.fixture @classmethod - @pytest.fixture() def plot(cls): return cls.create() From 7ebfe5ce2dddb5d35c7825997544e9c72a10c484 Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Mon, 19 Jan 2026 18:02:48 +0000 Subject: [PATCH 2/5] Fix handling of CacheEntry in all_adc() In the latest LALSuite, the lal.utils.CacheEntry object implements the __fspath__ method, so that os.path.basename and friends can operate directly on it --- gwsumm/data/timeseries.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gwsumm/data/timeseries.py b/gwsumm/data/timeseries.py index 6d03ac0e..7faf039e 100644 --- a/gwsumm/data/timeseries.py +++ b/gwsumm/data/timeseries.py @@ -393,10 +393,12 @@ def all_adc(cache): """ for path in cache: try: - tag = os.path.basename(path).split('-')[1] + path = os.path.basename(path) except (AttributeError, TypeError): # CacheEntry tag = path.description path = path.path + else: + tag = path.split('-')[1] if not path.endswith('.gwf') or tag not in ADC_TYPES: return False return True From bcfb47c8186071c36963685035bd130d91fa5b5d Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Mon, 19 Jan 2026 18:03:10 +0000 Subject: [PATCH 3/5] Protect against no channel at all when testing for trends --- gwsumm/data/timeseries.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gwsumm/data/timeseries.py b/gwsumm/data/timeseries.py index 7faf039e..54f12102 100644 --- a/gwsumm/data/timeseries.py +++ b/gwsumm/data/timeseries.py @@ -713,8 +713,11 @@ def _get_timeseries_dict(channels, segments, config=None, data.override_unit(channel.unit) # update channel type for trends - if data.channel.type is None and ( - data.channel.trend is not None): + if ( + data.channel is not None + and data.channel.type is None + and data.channel.trend is not None + ): if data.dt.to('s').value == 1: data.channel.type = 's-trend' elif data.dt.to('s').value == 60: From cedaa38704bf40f85ea79d05af3ed34eb1cc409f Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Mon, 19 Jan 2026 18:04:21 +0000 Subject: [PATCH 4/5] Always close the HDF5 archive file when checking we can open it --- gwsumm/archive.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gwsumm/archive.py b/gwsumm/archive.py index ad9d4ae3..a5aa324a 100644 --- a/gwsumm/archive.py +++ b/gwsumm/archive.py @@ -210,7 +210,8 @@ def read_data_archive(sourcefile, rm_source_on_fail=True): # down the whole workflow, requiring manual intervention. Here, we attempt # to automatically catch a common failure try: - h5file = File(sourcefile, 'r') + with File(sourcefile, 'r'): + pass except FileNotFoundError: raise except OSError as exc: # file is corrupt, so we remove it to start fresh From 4e5f9962450df21b08e79dc95127a23c0f4614e8 Mon Sep 17 00:00:00 2001 From: Duncan Macleod Date: Mon, 19 Jan 2026 18:05:59 +0000 Subject: [PATCH 5/5] Avoid using items() and values() views when reading HDF5 The items() and values() views acquire the h5py lock ('phil') while iterating, which means that any threading underneath that that attempts to read data ends up in a deadlock (see, e.g. https://gitlab.com/gwpy/gwpy/-/issues/1843) By iterating over the keys instead, we avoid an unnecessary outer lock and work around the potential hang --- gwsumm/archive.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/gwsumm/archive.py b/gwsumm/archive.py index a5aa324a..b2c6ac53 100644 --- a/gwsumm/archive.py +++ b/gwsumm/archive.py @@ -251,7 +251,8 @@ def read_data_archive(sourcefile, rm_source_on_fail=True): # -- timeseries ------------------------- - for dataset in h5file.get('timeseries', {}).values(): + for name in h5file.get('timeseries', {}): + dataset = h5file['timeseries'][name] ts = TimeSeries.read(dataset, format='hdf5') if (re.search(r'\.(rms|min|mean|max|n)\Z', ts.channel.name) and ts.sample_rate.value == 1.0): @@ -272,7 +273,8 @@ def read_data_archive(sourcefile, rm_source_on_fail=True): # -- statevector -- --------------------- - for dataset in h5file.get('statevector', {}).values(): + for name in h5file.get('statevector', {}): + dataset = h5file['statevector'][name] sv = StateVector.read(dataset, format='hdf5') sv.channel = get_channel(sv.channel) add_timeseries(sv, key=sv.channel.ndsname) @@ -280,9 +282,11 @@ def read_data_archive(sourcefile, rm_source_on_fail=True): # -- spectrogram ------------------------ for tag, add_ in zip( - ['spectrogram', 'coherence-components'], - [add_spectrogram, add_coherence_component_spectrogram]): - for key, dataset in h5file.get(tag, {}).items(): + ['spectrogram', 'coherence-components'], + [add_spectrogram, add_coherence_component_spectrogram], + ): + for key in h5file.get(tag, {}): + dataset = h5file[tag][key] key = key.rsplit(',', 1)[0] spec = Spectrogram.read(dataset, format='hdf5') spec.channel = get_channel(spec.channel) @@ -290,14 +294,16 @@ def read_data_archive(sourcefile, rm_source_on_fail=True): # -- segments --------------------------- - for name, dataset in h5file.get('segments', {}).items(): + for name in h5file.get('segments', {}): + dataset = h5file['segments'][name] dqflag = DataQualityFlag.read(h5file, path=dataset.name, format='hdf5') globalv.SEGMENTS += {name: dqflag} # -- triggers --------------------------- - for dataset in h5file.get('triggers', {}).values(): + for name in h5file.get('triggers', {}): + dataset = h5file['triggers'][name] load_table(dataset)