-
Notifications
You must be signed in to change notification settings - Fork 9
Update for iprox downloads. #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
1414449
8a56efc
b91613e
14b00f9
6dc27af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from concurrent.futures import ThreadPoolExecutor, as_completed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import defusedxml.ElementTree as ET | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import ClassVar, Dict, List, Optional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from urllib.parse import urlparse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,6 +25,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.download import registry | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.download.base import Provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.download.jpost import JpostProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.download.transport import _safe_join | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @registry.register | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -34,6 +37,9 @@ class IproxProvider(Provider): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PX_XML_URL_TEMPLATE: ClassVar[str] = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "http://download.iprox.org/{accession}/PX_{accession}.xml" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ASPERA_HOST: ClassVar[str] = "download.iprox.org" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ASPERA_PORT: ClassVar[str] = "33001" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ASPERA_ROOT: ClassVar[str] = "/data/iprox" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # iProX PX XML uses the same PSI-MS cvParam "name" values as JPOST PROXI, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # so we reuse JpostProvider's category map. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PX_CATEGORY_MAP: ClassVar[Dict[str, str]] = JpostProvider.PROXI_CATEGORY_MAP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +51,134 @@ def matches(accession: str) -> bool: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return bool(re.fullmatch(r"IPX\d{7,10}", accession.upper())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _ascp_binary() -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Reuse PRIDE's bundled ascp binary resolution. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.download.pride import PrideProvider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return PrideProvider.get_ascp_binary() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _aspera_download_one( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ascp: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relpath: Optional[str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output_folder: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maximum_bandwidth: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_if_downloaded_already: bool, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env: Dict[str, str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Optional[str]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Download a single URL via ascp. Returns ``url`` on failure, else None.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| path = urlparse(url).path.lstrip("/") # e.g. IPX.../.../a.raw | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source = f"{user}@{cls.ASPERA_HOST}:{cls.ASPERA_ROOT}/{path}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the target file and nearby callers
git ls-files pridepy/download/iprox.py pridepy | sed -n '1,120p'
wc -l pridepy/download/iprox.py
cat -n pridepy/download/iprox.py | sed -n '1,220p'
# Find all references to aspera_download and ASPERA_HOST/ASPERA_ROOT
rg -n "aspera_download|ASPERA_HOST|ASPERA_ROOT|urlparse\\(" pridepy
# Look for any upstream URL validation in the PX router or related download entrypoints
rg -n "iprox|PX router|parse.*url|url.*host|hostname|scheme|..|safe path|aspera" pridepyRepository: PRIDE-Archive/pridepy Length of output: 50378 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the target file with line numbers
wc -l pridepy/download/iprox.py
cat -n pridepy/download/iprox.py | sed -n '1,220p'
# Show only the call sites and references around iProX/Aspera handling
rg -n -A4 -B4 "aspera_download|ASPERA_HOST|ASPERA_ROOT|urlparse\\(|iprox" pridepy | sed -n '1,260p'
# Narrowly inspect the downloader/router path if present
rg -n -A20 -B10 "class .*IPROX|def .*aspera|def .*download|router|PX" pridepy/download pridepy | sed -n '1,260p'Repository: PRIDE-Archive/pridepy Length of output: 44343 Validate the iProX URL before building the Aspera source path. 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if relpath: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dest = _safe_join(output_folder, relpath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dest = os.path.join(output_folder, os.path.basename(urlparse(url).path)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dest_parent = os.path.dirname(dest) or output_folder | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.makedirs(dest_parent, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_if_downloaded_already | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and os.path.isfile(dest) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| and os.path.getsize(dest) > 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.info(f"Skipping download as file already exists: {dest}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| argv = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ascp, "-QT", "-P", cls.ASPERA_PORT, "-l", maximum_bandwidth, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "-k", "2", source, dest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Aspera: %s -> %s", source.replace(password, "***"), dest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subprocess.run(argv, check=True, env=env) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Avoid marking partial Aspera outputs as complete. If 💾 Suggested partial-file flow if (
skip_if_downloaded_already
and os.path.isfile(dest)
and os.path.getsize(dest) > 0
):
logging.info(f"Skipping download as file already exists: {dest}")
return None
+ transfer_dest = f"{dest}.part"
argv = [
ascp, "-QT", "-P", cls.ASPERA_PORT, "-l", maximum_bandwidth,
- "-k", "2", source, dest,
+ "-k", "2", source, transfer_dest,
]
logging.info(
"Aspera: %s -> %s", source.replace(password, "***"), dest
)
try:
subprocess.run(argv, check=True, env=env)
+ os.replace(transfer_dest, dest)
return None📝 Committable suggestion
Suggested change
🧰 Tools🪛 ast-grep (0.44.0)[error] 96-96: Use of unsanitized data to create processes (os-system-unsanitized-data) [error] 96-96: Command coming from incoming request (subprocess-from-request) 🪛 Ruff (0.15.20)[error] 97-97: (S603) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except subprocess.CalledProcessError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.error(f"iProX Aspera failed for {url}: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def aspera_download( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cls, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| urls: List[str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output_folder: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relative_paths: List[Optional[str]], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user: Optional[str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password: Optional[str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maximum_bandwidth: str = "100M", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_if_downloaded_already: bool = False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parallel_files: int = 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Download iProX-hosted URLs via ascp on port 33001. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Requires iProX account credentials; the password is passed to the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subprocess through ASPERA_SCP_PASS (never argv). When | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``parallel_files`` > 1, transfers run concurrently: each ``ascp`` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| invocation is its own subprocess writing its own destination file, so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this is safe. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not user or not password: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "iProX Aspera requires credentials: pass --iprox-user and set " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "IPROX_ASPERA_PASSWORD (or answer the password prompt), or use " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "the default parallel HTTP transport instead." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ascp = cls._ascp_binary() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env = dict(os.environ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env["ASPERA_SCP_PASS"] = password | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.makedirs(output_folder, exist_ok=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed: List[str] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workers = max(1, min(parallel_files, len(urls))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workers > 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with ThreadPoolExecutor(max_workers=workers) as executor: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+134
to
+136
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Cap Aspera subprocess concurrency too.
🚦 Suggested worker cap+ MAX_ASPERA_PARALLEL_FILES: ClassVar[int] = 8
+
- workers = max(1, min(parallel_files, len(urls)))
+ requested_workers = max(1, min(parallel_files, len(urls)))
+ workers = min(requested_workers, cls.MAX_ASPERA_PARALLEL_FILES)
+ if workers < requested_workers:
+ logging.warning(
+ "Reducing iProX Aspera parallel_files from %d to %d",
+ requested_workers,
+ workers,
+ )
if workers > 1:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| future_to_url = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| executor.submit( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cls._aspera_download_one, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ascp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relative_paths[idx] if idx < len(relative_paths) else None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output_folder, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maximum_bandwidth, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_if_downloaded_already, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for idx, url in enumerate(urls) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for future in as_completed(future_to_url): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url = future_to_url[future] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = future.result() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if result is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed.append(result) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.error(f"iProX Aspera failed for {url}: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed.append(url) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for idx, url in enumerate(urls): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relpath = relative_paths[idx] if idx < len(relative_paths) else None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = cls._aspera_download_one( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ascp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| relpath, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output_folder, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| password, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maximum_bandwidth, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| skip_if_downloaded_already, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| env, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if result is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed.append(result) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if failed: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise RuntimeError( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"iProX Aspera download failed for {len(failed)} file(s): {failed}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_public_root(accession: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return f"/{accession.upper()}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,6 +19,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pridepy.util.api_handling import Util | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Combined cap on parallel_files (-w) x download_threads (-t): each factor is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # independently clamped to 32, but nested they can reach 1024 concurrent HTTP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # connections. Clamp the product to keep peak connections reasonable. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MAX_TOTAL_HTTP_CONNECTIONS = 64 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _safe_join(output_folder: str, relative_path: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Join ``output_folder`` with a dataset-relative path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -845,6 +850,19 @@ def _rel(idx: int) -> Optional[str]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| failed: List[str] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workers = max(1, min(parallel_files, len(http_urls))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workers * download_threads > MAX_TOTAL_HTTP_CONNECTIONS: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clamped_threads = max(1, MAX_TOTAL_HTTP_CONNECTIONS // workers) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "parallel_files (%d) x download_threads (%d) = %d exceeds the " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "combined connection cap of %d; reducing download_threads to %d " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "to keep peak HTTP connections bounded.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| download_threads, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workers * download_threads, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MAX_TOTAL_HTTP_CONNECTIONS, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clamped_threads, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| download_threads = clamped_threads | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
852
to
+865
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Files of interest =="
git ls-files pridepy/download/transport.py pridepy.py pridepy | sed -n '1,200p'
echo
echo "== transport.py around target lines =="
nl -ba pridepy/download/transport.py | sed -n '820,900p'
echo
echo "== Search for cap-related symbols and entry points =="
rg -n "MAX_TOTAL_HTTP_CONNECTIONS|parallel_files|download_threads|workers|download_http_urls|argparse|click|typer|fire|--parallel|--threads|-w|-t" pridepy . -g '!**/.git/**'Repository: PRIDE-Archive/pridepy Length of output: 3029 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path("pridepy/download/transport.py")
lines = path.read_text().splitlines()
for start, end in [(1,120), (820,900)]:
print(f"== {path} lines {start}-{end} ==")
for i in range(start, min(end, len(lines)) + 1):
print(f"{i:4d}: {lines[i-1]}")
print()
PY
echo "== Search CLI entry points and validations =="
python3 - <<'PY'
from pathlib import Path
import re
targets = [
Path("pridepy/pridepy.py"),
Path("pridepy/download/client.py"),
Path("pridepy/download/base.py"),
Path("pridepy/download/by_url.py"),
Path("pridepy/tests/test_cli_flatten.py"),
]
for p in targets:
if p.exists():
print(f"\n## {p}")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
if re.search(r'parallel_files|download_threads|MAX_TOTAL_HTTP_CONNECTIONS|--parallel|--threads|-w\b|-t\b|argparse|click|typer|fire', line):
print(f"{i:4d}: {line}")
PY
echo
echo "== Broader symbol search =="
rg -n "parallel_files|download_threads|MAX_TOTAL_HTTP_CONNECTIONS|download_http_urls|--parallel|--threads|-w\\b|-t\\b|argparse|click|typer|fire" pridepy -g '!**/.git/**'Repository: PRIDE-Archive/pridepy Length of output: 46710 Clamp
🔧 Proposed fix- workers = max(1, min(parallel_files, len(http_urls)))
+ workers = max(1, min(parallel_files, len(http_urls), MAX_TOTAL_HTTP_CONNECTIONS))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if workers > 1: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
851
to
866
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.info( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Downloading {len(http_urls)} HTTP(S) file(s) with {workers} parallel workers" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2. Iprox creds text ambiguous
🐞 Bug⚙ MaintainabilityAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools