From 56d6e151c99677884ef38e60e17c0b049e345b95 Mon Sep 17 00:00:00 2001 From: singjc Date: Sat, 17 Jan 2026 10:49:30 -0500 Subject: [PATCH 1/3] feat: enhance download_file function with backup URL support and improved error handling --- pyopenms_viz/util.py | 79 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/pyopenms_viz/util.py b/pyopenms_viz/util.py index 62f23905a..5417a2565 100644 --- a/pyopenms_viz/util.py +++ b/pyopenms_viz/util.py @@ -1,25 +1,80 @@ import os +import requests -def download_file(url, local_path): +def download_file(url, local_path, backup_url=None): """ Download a file from a URL if it does not exist locally. - url (str): The URL to download the file from. - local_path (str): The local path to save the file to. Does nothing if the file already exists. + Args: + url (str): The primary URL to download the file from. + local_path (str): The local path to save the file to. Does nothing if the file already exists. + backup_url (str, optional): A backup URL to try if the primary URL fails. """ - if not os.path.exists(local_path): - import requests + if os.path.exists(local_path): + return - response = requests.get(url, timeout=30, headers={"User-Agent": "Mozilla/5.0"}) - response.raise_for_status() + urls_to_try = [url] + if backup_url: + urls_to_try.append(backup_url) - # Detect if file is binary (e.g., .zip files) - is_binary = local_path.endswith((".zip", ".gz", ".tar")) - mode = "wb" if is_binary else "w" + last_error = None + for i, try_url in enumerate(urls_to_try): + try: + response = requests.get( + try_url, timeout=30, headers={"User-Agent": "Mozilla/5.0"} + ) + response.raise_for_status() - with open(local_path, mode) as f: - f.write(response.content if is_binary else response.text) + # Detect if file is binary (e.g., .zip files) + is_binary = local_path.endswith((".zip", ".gz", ".tar")) + mode = "wb" if is_binary else "w" + + with open(local_path, mode) as f: + f.write(response.content if is_binary else response.text) + return # Success, exit the function + + except requests.exceptions.HTTPError as e: + last_error = e + error_msg = ( + f"Failed to download from URL ({i + 1}/{len(urls_to_try)}): {try_url}\n" + f" HTTP Status: {e.response.status_code}\n" + f" Reason: {e.response.reason}" + ) + # Check for common error codes and provide helpful messages + if e.response.status_code == 403: + error_msg += ( + "\n Note: 403 Forbidden often means the server is blocking automated requests " + "(rate limiting, bot detection, or access restrictions)." + ) + elif e.response.status_code == 404: + error_msg += ( + "\n Note: 404 Not Found - the file may have been moved or deleted." + ) + + if i < len(urls_to_try) - 1: + print(f"Warning: {error_msg}\n Trying backup URL...") + else: + print(f"Error: {error_msg}") + + except requests.exceptions.RequestException as e: + last_error = e + error_msg = ( + f"Failed to download from URL ({i + 1}/{len(urls_to_try)}): {try_url}\n" + f" Error: {type(e).__name__}: {e}" + ) + if i < len(urls_to_try) - 1: + print(f"Warning: {error_msg}\n Trying backup URL...") + else: + print(f"Error: {error_msg}") + + # If we get here, all URLs failed + raise RuntimeError( + f"Failed to download '{local_path}' from all provided URLs.\n" + f" Primary URL: {url}\n" + + (f" Backup URL: {backup_url}\n" if backup_url else "") + + f" Last error: {last_error}" + ) def unzip_file(zip_path, extract_to): From 146cf0cb5b906bc63cd6a7df4a25a4a7e742efe3 Mon Sep 17 00:00:00 2001 From: singjc Date: Sat, 17 Jan 2026 10:49:56 -0500 Subject: [PATCH 2/3] feat: update download_file calls to include backup URLs for improved reliability --- docs/gallery_scripts_template/plot_chromatogram.py | 6 ++++-- ...plot_investigate_spectrum_binning_ms_matplotlib.py | 6 ++++-- ...pt_d_fructose_spectrum_prediction_ms_matplotlib.py | 11 ++++++----- docs/gallery_scripts_template/plot_mobilogram.py | 9 ++++++--- docs/gallery_scripts_template/plot_peakmap.py | 8 ++++++-- docs/gallery_scripts_template/plot_peakmap_3D.py | 8 ++++++-- ...lot_peakmap_binning_demonstration_ms_matplotlib.py | 9 ++++++--- .../plot_peakmap_marginals.py | 8 ++++++-- docs/gallery_scripts_template/plot_spectrum.py | 6 ++++-- docs/gallery_scripts_template/plot_spectrum_dia.py | 8 ++++++-- .../plot_spyogenes_subplots_ms_bokeh.py | 8 ++++++-- .../plot_spyogenes_subplots_ms_matplotlib.py | 8 ++++++-- .../plot_spyogenes_subplots_ms_plotly.py | 8 ++++++-- 13 files changed, 72 insertions(+), 31 deletions(-) diff --git a/docs/gallery_scripts_template/plot_chromatogram.py b/docs/gallery_scripts_template/plot_chromatogram.py index f19a2fe23..bd8c4f14f 100644 --- a/docs/gallery_scripts_template/plot_chromatogram.py +++ b/docs/gallery_scripts_template/plot_chromatogram.py @@ -10,9 +10,11 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestChromatogramDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestChromatogramDf.tsv" +backup_url = "https://zenodo.org/records/17904352/files/ionMobilityTestChromatogramDf.tsv?download=1" local_path = "ionMobilityTestChromatogramDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") df.plot( diff --git a/docs/gallery_scripts_template/plot_investigate_spectrum_binning_ms_matplotlib.py b/docs/gallery_scripts_template/plot_investigate_spectrum_binning_ms_matplotlib.py index 8c387f240..cee1dc65a 100644 --- a/docs/gallery_scripts_template/plot_investigate_spectrum_binning_ms_matplotlib.py +++ b/docs/gallery_scripts_template/plot_investigate_spectrum_binning_ms_matplotlib.py @@ -11,9 +11,11 @@ pd.options.plotting.backend = "ms_matplotlib" -url = "https://zenodo.org/records/17904352/files/TestSpectrumDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/TestSpectrumDf.tsv" +backup_url = "https://zenodo.org/records/17904352/files/TestSpectrumDf.tsv?download=1" local_path = "TestSpectrumDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") # Let's assess the peak binning and create a 4 by 2 subplot to visualize the different methods of binning diff --git a/docs/gallery_scripts_template/plot_manuscript_d_fructose_spectrum_prediction_ms_matplotlib.py b/docs/gallery_scripts_template/plot_manuscript_d_fructose_spectrum_prediction_ms_matplotlib.py index a18e57d4e..b8f70049e 100644 --- a/docs/gallery_scripts_template/plot_manuscript_d_fructose_spectrum_prediction_ms_matplotlib.py +++ b/docs/gallery_scripts_template/plot_manuscript_d_fructose_spectrum_prediction_ms_matplotlib.py @@ -49,12 +49,13 @@ from pyopenms_viz.util import download_file, unzip_file -# URL of the ZIP file -url = "https://zenodo.org/records/17904512/files/d_fructose_example.zip?download=1" - -# Download and extract the ZIP file +# GitHub release asset (primary) with Zenodo as backup +url = "https://github.com/OpenMS/pyopenms_viz/releases/download/manuscript/d_fructose_example.zip" +backup_url = ( + "https://zenodo.org/records/17904512/files/d_fructose_example.zip?download=1" +) zip_filename = "d_fructose_example.zip" -download_file(url, zip_filename) +download_file(url, zip_filename, backup_url=backup_url) unzip_file(zip_filename, ".") # Extract to current directory diff --git a/docs/gallery_scripts_template/plot_mobilogram.py b/docs/gallery_scripts_template/plot_mobilogram.py index 4289fca5a..747bac6fb 100644 --- a/docs/gallery_scripts_template/plot_mobilogram.py +++ b/docs/gallery_scripts_template/plot_mobilogram.py @@ -6,15 +6,18 @@ This example shows how to use different approaches. """ -import os import pandas as pd from pyopenms_viz.util import download_file pd.options.plotting.backend = "TEMPLATE" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestFeatureDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +) local_path = "ionMobilityTestFeatureDf.tsv" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") df.plot( diff --git a/docs/gallery_scripts_template/plot_peakmap.py b/docs/gallery_scripts_template/plot_peakmap.py index 5e36ee1ff..e933cbda7 100644 --- a/docs/gallery_scripts_template/plot_peakmap.py +++ b/docs/gallery_scripts_template/plot_peakmap.py @@ -10,9 +10,13 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestFeatureDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +) local_path = "ionMobilityTestFeatureDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") # Code to plot a peakmap diff --git a/docs/gallery_scripts_template/plot_peakmap_3D.py b/docs/gallery_scripts_template/plot_peakmap_3D.py index d36b41fa4..c611828f4 100644 --- a/docs/gallery_scripts_template/plot_peakmap_3D.py +++ b/docs/gallery_scripts_template/plot_peakmap_3D.py @@ -10,9 +10,13 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestFeatureDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +) local_path = "ionMobilityTestFeatureDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") # Code to plot a peakmap diff --git a/docs/gallery_scripts_template/plot_peakmap_binning_demonstration_ms_matplotlib.py b/docs/gallery_scripts_template/plot_peakmap_binning_demonstration_ms_matplotlib.py index f19a3ce17..0b64b1236 100644 --- a/docs/gallery_scripts_template/plot_peakmap_binning_demonstration_ms_matplotlib.py +++ b/docs/gallery_scripts_template/plot_peakmap_binning_demonstration_ms_matplotlib.py @@ -11,9 +11,13 @@ pd.options.plotting.backend = "ms_matplotlib" -url = "https://zenodo.org/records/17904352/files/TestMSExperimentDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/TestMSExperimentDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/TestMSExperimentDf.tsv?download=1" +) local_path = "TestMSExperimentDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") @@ -23,7 +27,6 @@ binning_levels = [(10, 10), (40, 40), (100, 100)] for ax, (num_x_bins, num_y_bins) in zip(axs, binning_levels): - df.plot( kind="peakmap", x="RT", diff --git a/docs/gallery_scripts_template/plot_peakmap_marginals.py b/docs/gallery_scripts_template/plot_peakmap_marginals.py index 06dd61827..51257063f 100644 --- a/docs/gallery_scripts_template/plot_peakmap_marginals.py +++ b/docs/gallery_scripts_template/plot_peakmap_marginals.py @@ -11,9 +11,13 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestFeatureDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +) local_path = "ionMobilityTestFeatureDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") df.plot( diff --git a/docs/gallery_scripts_template/plot_spectrum.py b/docs/gallery_scripts_template/plot_spectrum.py index f0043dbda..ca56b0105 100644 --- a/docs/gallery_scripts_template/plot_spectrum.py +++ b/docs/gallery_scripts_template/plot_spectrum.py @@ -11,9 +11,11 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/TestSpectrumDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/TestSpectrumDf.tsv" +backup_url = "https://zenodo.org/records/17904352/files/TestSpectrumDf.tsv?download=1" local_path = "TestSpectrumDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") # mirror a reference spectrum with ion and sequence annoations diff --git a/docs/gallery_scripts_template/plot_spectrum_dia.py b/docs/gallery_scripts_template/plot_spectrum_dia.py index 6c647c9d9..68c9a58d8 100644 --- a/docs/gallery_scripts_template/plot_spectrum_dia.py +++ b/docs/gallery_scripts_template/plot_spectrum_dia.py @@ -10,9 +10,13 @@ pd.options.plotting.backend = "TEMPLATE" -url = "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +# GitHub raw URL (primary) with Zenodo as backup +url = "https://raw.githubusercontent.com/OpenMS/pyopenms_viz/main/test/test_data/ionMobilityTestFeatureDf.tsv" +backup_url = ( + "https://zenodo.org/records/17904352/files/ionMobilityTestFeatureDf.tsv?download=1" +) local_path = "ionMobilityTestFeatureDf.tsv" -download_file(url, local_path) +download_file(url, local_path, backup_url=backup_url) df = pd.read_csv(local_path, sep="\t") df.plot( diff --git a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_bokeh.py b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_bokeh.py index 0fca6d9ac..286a5bfb0 100644 --- a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_bokeh.py +++ b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_bokeh.py @@ -11,11 +11,15 @@ from bokeh.io import show from pyopenms_viz.util import download_file, unzip_file +# GitHub release asset (primary) with Zenodo as backup +url = ( + "https://github.com/OpenMS/pyopenms_viz/releases/download/manuscript/spyogenes.zip" +) +backup_url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" zip_filename = "spyogenes.zip" zip_dir = "spyogenes" -url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" -download_file(url, zip_filename) +download_file(url, zip_filename, backup_url=backup_url) unzip_file(zip_filename, ".") # Extract to current directory annotation_bounds = pd.read_csv( diff --git a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_matplotlib.py b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_matplotlib.py index bfaecd12b..be05d6115 100644 --- a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_matplotlib.py +++ b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_matplotlib.py @@ -12,11 +12,15 @@ pd.options.plotting.backend = "ms_matplotlib" +# GitHub release asset (primary) with Zenodo as backup +url = ( + "https://github.com/OpenMS/pyopenms_viz/releases/download/manuscript/spyogenes.zip" +) +backup_url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" zip_filename = "spyogenes.zip" zip_dir = "spyogenes" -url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" -download_file(url, zip_filename) +download_file(url, zip_filename, backup_url=backup_url) unzip_file(zip_filename, ".") # Extract to current directory chrom_df = pd.read_csv( diff --git a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_plotly.py b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_plotly.py index 26e6a6920..89027bb0c 100644 --- a/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_plotly.py +++ b/docs/gallery_scripts_template/plot_spyogenes_subplots_ms_plotly.py @@ -10,11 +10,15 @@ from plotly.subplots import make_subplots from pyopenms_viz.util import download_file, unzip_file +# GitHub release asset (primary) with Zenodo as backup +url = ( + "https://github.com/OpenMS/pyopenms_viz/releases/download/manuscript/spyogenes.zip" +) +backup_url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" zip_filename = "spyogenes.zip" zip_dir = "spyogenes" -url = "https://zenodo.org/records/17904512/files/spyogenes.zip?download=1" -download_file(url, zip_filename) +download_file(url, zip_filename, backup_url=backup_url) unzip_file(zip_filename, ".") # Extract to current directory annotation_bounds = pd.read_csv( From 4aa22519b8ba894888640aa6ffbd42a6d66098ef Mon Sep 17 00:00:00 2001 From: singjc Date: Sat, 17 Jan 2026 10:52:46 -0500 Subject: [PATCH 3/3] feat: add concurrency settings to CI workflows for improved run management --- .github/workflows/ci.yml | 5 +++++ .github/workflows/execute_notebooks.yml | 5 +++++ .github/workflows/static.yml | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee995a37..1616a846f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,11 @@ name: continuous-integration on: [push, pull_request] +# Cancel in-progress runs when a new push is made to the same branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/execute_notebooks.yml b/.github/workflows/execute_notebooks.yml index d1c8dd587..b887571c3 100644 --- a/.github/workflows/execute_notebooks.yml +++ b/.github/workflows/execute_notebooks.yml @@ -16,6 +16,11 @@ on: - '.github/workflows/execute_notebooks.yml' workflow_dispatch: # Allow manual trigger +# Cancel in-progress runs when a new push is made to the same branch/PR +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: execute-notebooks: runs-on: ubuntu-latest diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index c6f663636..f8ff221fc 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -22,10 +22,10 @@ permissions: id-token: write pull-requests: write -# Allow only one concurrent deployment per branch/PR +# Allow only one concurrent deployment per branch/PR, cancel in-progress runs concurrency: group: "pages-${{ github.ref }}" - cancel-in-progress: false + cancel-in-progress: true jobs: # Build job - runs for both main branch and PRs