Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/execute_notebooks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions docs/gallery_scripts_template/plot_chromatogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
9 changes: 6 additions & 3 deletions docs/gallery_scripts_template/plot_mobilogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 6 additions & 2 deletions docs/gallery_scripts_template/plot_peakmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions docs/gallery_scripts_template/plot_peakmap_3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand All @@ -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",
Expand Down
8 changes: 6 additions & 2 deletions docs/gallery_scripts_template/plot_peakmap_marginals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 4 additions & 2 deletions docs/gallery_scripts_template/plot_spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions docs/gallery_scripts_template/plot_spectrum_dia.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
79 changes: 67 additions & 12 deletions pyopenms_viz/util.py
Original file line number Diff line number Diff line change
@@ -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:
Comment on lines +39 to +50

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

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

When catching requests.exceptions.HTTPError, the code accesses e.response.status_code and e.response.reason without checking if e.response is not None. While HTTPError typically has a response attribute populated, it's better to add a defensive check to prevent potential AttributeError if the response attribute is None in edge cases.

Suggested change
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:
resp = e.response
status_code = resp.status_code if resp is not None else "N/A"
reason = resp.reason if resp is not None else "No response available"
error_msg = (
f"Failed to download from URL ({i + 1}/{len(urls_to_try)}): {try_url}\n"
f" HTTP Status: {status_code}\n"
f" Reason: {reason}"
)
# Check for common error codes and provide helpful messages
if resp is not None and resp.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 resp is not None and resp.status_code == 404:

Copilot uses AI. Check for mistakes.
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}")
Comment on lines +55 to +58

Copilot AI Jan 17, 2026

Copy link

Choose a reason for hiding this comment

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

The error messages on lines 56 and 58 will include the detailed error_msg which is already marked as either "Warning" or "Error" based on whether there are more URLs to try. However, the prefix in the print statements adds another "Warning:" or "Error:" label, resulting in duplicate labeling. Consider simplifying the print statements to avoid redundancy, such as using just print(error_msg) when i == len(urls_to_try) - 1, since error_msg doesn't include the "Warning:" or "Error:" prefix itself.

Copilot uses AI. Check for mistakes.

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):
Expand Down
Loading