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
2 changes: 1 addition & 1 deletion modules/bigbio/pridepy/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ channels:
- bioconda
- defaults
dependencies:
- bioconda::pridepy=0.0.14
- bioconda::pridepy=0.0.15

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Is bioconda::pridepy 0.0.15 available on Bioconda or Anaconda?

💡 Result:

No, bioconda::pridepy 0.0.15 is not available on Bioconda or Anaconda. pridepy 0.0.15 exists on PyPI [1][2], but searches of Anaconda.org/bioconda show no pridepy package [3][4][5][6][7], and Bioconda recipe docs and GitHub do not list a pridepy recipe [8][9][10][11][12][13]. A Biocontainer exists on quay.io [14], but no conda package in bioconda channel. GitHub repo latest release is v0.0.12 [15], though PyPI has 0.0.15.

Citations:


Remove bioconda:: channel prefix or update the package source.

The package pridepy=0.0.15 does not exist in the Bioconda channel. It is only available on PyPI. Using bioconda::pridepy=0.0.15 will cause conda environment resolution to fail at runtime. Either specify the package with the pip package manager instead, or use a version that exists in Bioconda (if available). The latest GitHub release is v0.0.12.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/bigbio/pridepy/environment.yml` at line 6, The environment.yml
currently pins bioconda::pridepy=0.0.15 which doesn't exist in Bioconda; update
modules/bigbio/pridepy/environment.yml to remove the bioconda:: prefix and
install pridepy via pip (or change to an available Bioconda version), e.g., move
"pridepy=0.0.15" into the pip section or replace with a Bioconda-available
version (e.g., v0.0.12) so conda environment resolution succeeds; target
symbols: pridepy and environment.yml.

6 changes: 3 additions & 3 deletions modules/bigbio/pridepy/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ process PRIDEPY_DOWNLOAD {

conda "${moduleDir}/environment.yml"
container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container
? 'https://depot.galaxyproject.org/singularity/pridepy:0.0.14--pyhdfd78af_0'
: 'biocontainers/pridepy:0.0.14--pyhdfd78af_0'}"
? 'https://depot.galaxyproject.org/singularity/pridepy:0.0.15--pyhdfd78af_0'
: 'biocontainers/pridepy:0.0.15--pyhdfd78af_0'}"
Comment on lines 7 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Singularity depot URL availability
curl -sI "https://depot.galaxyproject.org/singularity/pridepy:0.0.15--pyhdfd78af_0" \
  | head -5

# Check Docker Hub / BioContainers tag availability
curl -s "https://registry.hub.docker.com/v2/repositories/biocontainers/pridepy/tags/0.0.15--pyhdfd78af_0/" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('name','NOT FOUND'))"

Repository: bigbio/nf-modules

Length of output: 214


Fix Docker image reference: biocontainers/pridepy:0.0.15--pyhdfd78af_0 does not exist.

The Singularity image exists (HTTP 200), but the Docker/BioContainers tag does not exist in the registry. The workflow will fail when attempting to pull the Docker image. Verify the correct build hash for the 0.0.15 release on BioContainers and update line 9 accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/bigbio/pridepy/main.nf` around lines 7 - 9, The Docker/BioContainers
tag 'biocontainers/pridepy:0.0.15--pyhdfd78af_0' referenced in the container
expression (the ternary using workflow.containerEngine == 'singularity' &&
!task.ext.singularity_pull_docker_container) is invalid; replace the Docker
branch of that ternary with the correct BioContainers tag for pridepy 0.0.15
(use the build hash found on BioContainers/Quay/Docker Hub for that release) or
point both branches to the working Singularity URL; update the string after the
colon so the expression uses a valid image name.


input:
val(meta)
Expand Down Expand Up @@ -39,7 +39,7 @@ process PRIDEPY_DOWNLOAD {

cat <<-END_VERSIONS > versions.yml
"${task.process}":
pridepy: 0.0.14
pridepy: 0.0.15
END_VERSIONS
"""
}
4 changes: 2 additions & 2 deletions modules/bigbio/pridepy/tests/main.nf.test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
]
],
[
"versions.yml:md5,c27ccb04c91f92f188b7f4cf2d54ed49"
"versions.yml:md5,19782821de060ab1588d5d4274bdacf0"
]
],
"timestamp": "2026-04-28T18:15:37.876014654",
Expand Down Expand Up @@ -38,7 +38,7 @@
]
],
[
"versions.yml:md5,c27ccb04c91f92f188b7f4cf2d54ed49"
"versions.yml:md5,19782821de060ab1588d5d4274bdacf0"
]
],
"timestamp": "2026-04-28T18:15:43.996367071",
Expand Down
7 changes: 7 additions & 0 deletions modules/bigbio/qpx/environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
channels:
- conda-forge
- bioconda
- defaults
dependencies:
- pip:
- qpx
Comment on lines +5 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
import json, urllib.request
data = json.load(urllib.request.urlopen("https://pypi.org/pypi/qpx/json"))
print("latest:", data["info"]["version"])
print("1.0.2 exists:", "1.0.2" in data.get("releases", {}))
PY

Repository: bigbio/nf-modules

Length of output: 2643


🏁 Script executed:

# Check if the file exists and verify its current state
cat -n modules/bigbio/qpx/environment.yml

Repository: bigbio/nf-modules

Length of output: 197


🌐 Web query:

qpx python package pypi version 1.0.2

💡 Result:

For the Python package named “qpx” on PyPI (BigBio’s QPX: Quantitative Proteomics eXchange), version 1.0.2 does not appear to be published. The PyPI project page shows a version history with 1.0.1 (released 2026-04-03) and 1.0.0 (released 2026-04-01), but no 1.0.2 entry is listed.[1] What you can do: 1) Check whether you meant a different “qpx” package name (there are other unrelated “qpx” identifiers online).[2] 2) If you have a requirement for exactly 1.0.2, you may need to install from the package’s GitHub or request that the maintainer publish that release, since it is not present in PyPI’s listed versions.[1][3]

Citations:


🏁 Script executed:

# Check the container specification in main.nf
cat -n modules/bigbio/qpx/main.nf | head -30

Repository: bigbio/nf-modules

Length of output: 1176


Pin qpx in the Conda environment for reproducibility.

Line 7 uses an unpinned pip dependency, so Conda executions can drift from the tested container behavior and produce non-deterministic outcomes.

However, the proposed pin qpx==1.0.2 cannot be used—that version does not exist on PyPI. The latest available version is 1.0.1 (released 2026-04-03). Consider pinning to qpx==1.0.1 or investigating why the container version (1.0.2) differs from PyPI's published releases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/bigbio/qpx/environment.yml` around lines 5 - 7, The Conda environment
has an unpinned pip dependency "qpx" causing non-deterministic installs; update
the dependencies block to pin qpx to the confirmed PyPI release (qpx==1.0.1) or,
if the project requires features from the container's qpx==1.0.2, investigate
and document why the container has a non-PyPI version and either publish that
version or adjust the environment to match; modify the "qpx" entry in the
dependencies -> pip list to include the chosen pin and add a brief comment in
the file explaining any deviation if you opt to investigate/publish instead.

81 changes: 81 additions & 0 deletions modules/bigbio/qpx/main.nf
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
process QPX_EXPORT {
tag "qpx_export"
label 'process_medium'
label 'error_retry'

conda "${moduleDir}/environment.yml"
container "${workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container
? 'https://depot.galaxyproject.org/singularity/qpx:1.0.2--pyhdfd78af_1'
: 'biocontainers/qpx:1.0.2--pyhdfd78af_1'}"

input:
path(diann_report)
path(pg_matrix)
path(sdrf)
path(diann_log)
val(project_accession)

output:
path "qpx_output/" , emit: qpx_dataset
path "*.h5mu" , emit: mudata
path "versions.yml", emit: versions

when:
task.ext.when == null || task.ext.when

script:
def args = task.ext.args ?: ''
def prefix = project_accession ?: 'diann'
def pg_arg = pg_matrix ? "--pg-matrix-path ${pg_matrix}" : ''
def log_arg = diann_log ? "--diann-log ${diann_log}" : ''
def acc_arg = project_accession ? "--project-accession ${project_accession}" : ''
def qvalue = params.matrix_qvalue ?: 0.05
"""
set -o pipefail
qpxc convert diann \\
--report-path ${diann_report} \\
--sdrf-file ${sdrf} \\
${pg_arg} \\
${log_arg} \\
${acc_arg} \\
--output-folder qpx_output \\
--output-prefix ${prefix} \\
--qvalue-threshold ${qvalue} \\
--standardized-intensities \\
--duckdb-threads ${task.cpus} \\
--duckdb-max-memory ${task.memory ? task.memory.toGiga() : 4}GB \\
--compression zstd \\
${args}

python - <<'PY'
from qpx.dataset import Dataset
from qpx.mudata import build_mudata

ds = Dataset("qpx_output")
mdata = build_mudata(ds)
mdata.write("${prefix}.h5mu")
ds.close()
print(f"MuData: {mdata.n_obs} obs x {mdata.n_vars} vars -> ${prefix}.h5mu")
PY

cat <<-END_VERSIONS > versions.yml
"${task.process}":
qpx: \$(qpxc --version 2>&1 | sed 's/^qpx //')
mudata: \$(python -c 'import mudata; print(mudata.__version__)')
END_VERSIONS
"""

stub:
def prefix = project_accession ?: 'diann'
"""
mkdir -p qpx_output
touch qpx_output/stub.parquet
touch ${prefix}.h5mu

cat <<-END_VERSIONS > versions.yml
"${task.process}":
qpx: stub
mudata: stub
END_VERSIONS
"""
}
65 changes: 65 additions & 0 deletions modules/bigbio/qpx/meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: qpx_export
description: Convert DIA-NN quantification outputs to QPX Parquet dataset and MuData (.h5mu)
keywords:
- proteomics
- quantification
- parquet
- mudata
- dia-nn
- qpx
tools:
- qpx:
description: |
QPX (Quantitative Proteomics eXchange) converts tool-specific quantification
outputs into a standardised Parquet dataset and optionally a MuData (.h5mu) file
for downstream analysis in Python (scanpy / muon).
homepage: https://github.com/bigbio/qpx
documentation: https://github.com/bigbio/qpx
tool_dev_url: https://github.com/bigbio/qpx
licence:
- "Apache-2.0"
identifier: ""
input:
- diann_report:
type: file
description: DIA-NN main report (TSV or Parquet)
pattern: "*.{tsv,parquet}"
- pg_matrix:
type: file
description: DIA-NN protein-group matrix
pattern: "*.{tsv,parquet}"
- sdrf:
type: file
description: SDRF sample metadata file
pattern: "*.sdrf.tsv"
- diann_log:
type: file
description: DIA-NN summary log for version detection
pattern: "*.log"
Comment on lines +35 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Broaden the DIANN log pattern to match actual filenames.

Line 38 currently restricts logs to *.log, but the module test input uses report.log.txt. Aligning the pattern avoids metadata drift and user confusion.

Proposed fix
   - diann_log:
       type: file
       description: DIA-NN summary log for version detection
-      pattern: "*.log"
+      pattern: "*.log*"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- diann_log:
type: file
description: DIA-NN summary log for version detection
pattern: "*.log"
- diann_log:
type: file
description: DIA-NN summary log for version detection
pattern: "*.log*"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/bigbio/qpx/meta.yml` around lines 35 - 38, The diann_log metadata
currently uses a too-narrow glob ("*.log") so files like report.log.txt are not
matched; update the diann_log entry's pattern value (the pattern field under
diann_log) to a broader glob such as "*log*" or a multi-extension glob like
"*.{log,txt}" so that files like report.log.txt are captured by the metadata.

- project_accession:
type: string
description: PRIDE project accession (e.g. PXD026600) used as output prefix
output:
qpx_dataset:
- "qpx_output/":
type: directory
description: QPX Parquet dataset directory
pattern: "qpx_output/"
mudata:
- "*.h5mu":
type: file
description: MuData file containing the quantification data
pattern: "*.h5mu"
versions:
- versions.yml:
type: file
description: File containing software versions
pattern: "versions.yml"
ontologies:
- edam: http://edamontology.org/format_3750
authors:
- "@ypriverol"
- "@Shen-YuFei"
maintainers:
- "@ypriverol"
- "@Shen-YuFei"
88 changes: 88 additions & 0 deletions modules/bigbio/qpx/tests/main.nf.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
nextflow_process {

name "Test Process QPX_EXPORT"
script "../main.nf"
process "QPX_EXPORT"
tag "modules"
tag "modules_bigbio"
tag "modules_qpx"
tag "qpx"

config "./nextflow.config"

test("Should convert DIA-NN output to QPX Parquet + MuData") {

when {
def zipUrl = (params.test_data?.proteomics?.qpx?.diann_results) ?: "https://ftp.pride.ebi.ac.uk/pub/databases/pride/resources/proteomes/pmultiqc/example-projects/PXD063291.zip"
def zipFile = file("test_qpx.zip")
zipFile.withOutputStream { out ->
out << new URL(zipUrl).openStream()
}
def resultsDir = file("test_qpx_data")
resultsDir.mkdirs()
new java.util.zip.ZipFile(zipFile).withCloseable { zip ->
zip.entries().each { entry ->
if (!entry.isDirectory()) {
def extractFile = new File(resultsDir, entry.name)
extractFile.parentFile.mkdirs()
extractFile.withOutputStream { out ->
out << zip.getInputStream(entry)
}
}
}
}
Comment on lines +18 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden ZIP download/extraction (timeouts + Zip Slip guard).

Line 19 has no connect/read timeout, and Line 26 trusts archive entry paths without canonical-path validation. That can hang CI and permits path traversal from crafted ZIPs.

Proposed fix
-            zipFile.withOutputStream { out ->
-                out << new URL(zipUrl).openStream()
-            }
+            def connection = new URL(zipUrl).openConnection()
+            connection.setConnectTimeout(30_000)
+            connection.setReadTimeout(120_000)
+            connection.inputStream.withCloseable { input ->
+                zipFile.withOutputStream { out -> out << input }
+            }
             def resultsDir = file("test_qpx_data")
             resultsDir.mkdirs()
             new java.util.zip.ZipFile(zipFile).withCloseable { zip ->
+                def canonicalRoot = resultsDir.canonicalFile
                 zip.entries().each { entry ->
                     if (!entry.isDirectory()) {
-                        def extractFile = new File(resultsDir, entry.name)
-                        extractFile.parentFile.mkdirs()
-                        extractFile.withOutputStream { out ->
-                            out << zip.getInputStream(entry)
-                        }
+                        def extractFile = new File(resultsDir, entry.name).canonicalFile
+                        if (!extractFile.path.startsWith(canonicalRoot.path + File.separator)) {
+                            throw new SecurityException("Blocked zip entry outside target dir: ${entry.name}")
+                        }
+                        extractFile.parentFile.mkdirs()
+                        zip.getInputStream(entry).withCloseable { input ->
+                            extractFile.withOutputStream { out -> out << input }
+                        }
                     }
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zipFile.withOutputStream { out ->
out << new URL(zipUrl).openStream()
}
def resultsDir = file("test_qpx_data")
resultsDir.mkdirs()
new java.util.zip.ZipFile(zipFile).withCloseable { zip ->
zip.entries().each { entry ->
if (!entry.isDirectory()) {
def extractFile = new File(resultsDir, entry.name)
extractFile.parentFile.mkdirs()
extractFile.withOutputStream { out ->
out << zip.getInputStream(entry)
}
}
}
}
def connection = new URL(zipUrl).openConnection()
connection.setConnectTimeout(30_000)
connection.setReadTimeout(120_000)
connection.inputStream.withCloseable { input ->
zipFile.withOutputStream { out -> out << input }
}
def resultsDir = file("test_qpx_data")
resultsDir.mkdirs()
new java.util.zip.ZipFile(zipFile).withCloseable { zip ->
def canonicalRoot = resultsDir.canonicalFile
zip.entries().each { entry ->
if (!entry.isDirectory()) {
def extractFile = new File(resultsDir, entry.name).canonicalFile
if (!extractFile.path.startsWith(canonicalRoot.path + File.separator)) {
throw new SecurityException("Blocked zip entry outside target dir: ${entry.name}")
}
extractFile.parentFile.mkdirs()
zip.getInputStream(entry).withCloseable { input ->
extractFile.withOutputStream { out -> out << input }
}
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/bigbio/qpx/tests/main.nf.test` around lines 18 - 33, The ZIP
download/extraction needs timeouts and a zip-slip guard: replace the plain new
URL(zipUrl).openStream() used inside zipFile.withOutputStream and the direct
zip.getInputStream(entry) reads with URLConnection/HttpURLConnection that sets
connect and read timeouts before opening the stream, and ensure streams are
closed; when extracting entries from new java.util.zip.ZipFile(...) validate
each entry.name by resolving the destination File (extractFile) to its canonical
path and verifying it startsWith the canonical path of resultsDir (skip and log
any entries that don't), continue to create parent dirs and write only after the
canonical-path check to prevent path traversal and hangs.

zipFile.delete()

process {
"""
input[0] = file('${resultsDir.getAbsolutePath()}/PXD063291/DiaNN/report.tsv')
input[1] = file('${resultsDir.getAbsolutePath()}/PXD063291/DiaNN/report.pg_matrix.tsv')
input[2] = file('${resultsDir.getAbsolutePath()}/PXD063291/PXD063291.sdrf.tsv')
input[3] = file('${resultsDir.getAbsolutePath()}/PXD063291/DiaNN/report.log.txt')
input[4] = "PXD063291"
"""
}
}

then {
assertAll(
{ assert process.success },
{ assert process.out.qpx_dataset.size() == 1 },
{ assert process.out.mudata.size() == 1 },
{ assert process.out.mudata[0].toString().endsWith('.h5mu') },
{ assert snapshot(process.out.versions).match("versions") }
)
}

cleanup {
file("test_qpx_data").deleteDir()
}
}

test("Should run stub mode") {

options "-stub"

when {
process {
"""
input[0] = file("stub_report.tsv", checkIfExists: false)
input[1] = file("stub_pg_matrix.tsv", checkIfExists: false)
input[2] = file("stub_sample.sdrf.tsv", checkIfExists: false)
input[3] = file("stub_diann.log", checkIfExists: false)
input[4] = "PXD026600"
"""
}
}

then {
assert process.success
assert process.out.qpx_dataset.size() == 1
assert process.out.mudata.size() == 1
assert snapshot(
process.out.mudata,
process.out.versions
).match("stub")
}
}
}
17 changes: 17 additions & 0 deletions modules/bigbio/qpx/tests/main.nf.test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"stub": {
"content": [
[
"PXD026600.h5mu:md5,d41d8cd98f00b204e9800998ecf8427e"
],
[
"versions.yml:md5,cc457dc644e23b74317eaa02eedda495"
]
],
"timestamp": "2026-05-05T18:36:46.564300378",
"meta": {
"nf-test": "0.9.5",
"nextflow": "26.04.0"
}
}
}
10 changes: 10 additions & 0 deletions modules/bigbio/qpx/tests/nextflow.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
process {
withName: 'QPX_EXPORT' {
ext.args = ''
}
}

params {
qpx_version = 'dev'
matrix_qvalue = 0.05
}
4 changes: 4 additions & 0 deletions tests/config/test_data.config
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ params {
mzml = "https://ftp.pride.ebi.ac.uk/pub/databases/pride/resources/proteomes/quantms-ci-github/onsite/1.mzML"
idxml = "https://ftp.pride.ebi.ac.uk/pub/databases/pride/resources/proteomes/quantms-ci-github/onsite/1_consensus_fdr_filter_pep.idXML"
}
qpx {
// DIA-NN results for QPX export testing (reuses pmultiqc DIANN example)
diann_results = "https://ftp.pride.ebi.ac.uk/pub/databases/pride/resources/proteomes/pmultiqc/example-projects/PXD063291.zip"
}
}
}
}
Loading