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
18 changes: 9 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ on:

jobs:
pytest:
name: Unit tests (gacdi_manifest)
name: Unit tests (mcdi)
runs-on: ubuntu-latest
defaults:
run:
working-directory: cli_tools/gacdi_manifest
working-directory: cli_tools/mcdi
strategy:
matrix:
python-version: ["3.9", "3.11"]
Expand All @@ -36,9 +36,9 @@ jobs:
- name: Install planemo
run: python -m pip install planemo
- name: Lint manifest_gdc
run: planemo lint --skip citations tools/manifest_gdc
run: planemo lint tools/manifest_gdc
- name: Lint manifest_downloader
run: planemo lint --skip citations tools/manifest_downloader
run: planemo lint tools/manifest_downloader

planemo-test-manifest-downloader:
name: Test Galaxy tool (manifest_downloader)
Expand All @@ -54,19 +54,19 @@ jobs:
- name: Read version from package __init__.py
id: version
run: |
VERSION=$(grep -m1 '^__version__' cli_tools/gacdi_manifest/gacdi_manifest/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
VERSION=$(grep -m1 '^__version__' cli_tools/mcdi/mcdi/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

# Built locally and never pushed: this only has to exist for this job's
# own `planemo test --docker` step to resolve the tag manifest_downloader.xml
# references. The real, published image is built and pushed by
# containers.yml, only on merge to main.
- name: Build gacdi-manifest image (local only, not pushed)
- name: Build mcdi image (local only, not pushed)
run: |
docker build \
--build-arg GACDI_BUILD=${{ github.sha }} \
-t quay.io/goeckslab/gacdi-manifest:${{ steps.version.outputs.version }} \
cli_tools/gacdi_manifest
--build-arg MCDI_BUILD=${{ github.sha }} \
-t quay.io/goeckslab/mcdi:${{ steps.version.outputs.version }} \
cli_tools/mcdi

- name: Test
run: planemo test --docker tools/manifest_downloader
Expand Down
33 changes: 16 additions & 17 deletions .github/workflows/containers.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
name: Build and push manifest container

# Builds the manifest-builder image and pushes it to Quay whenever
# cli_tools/gacdi_manifest changes on main, or manually. The image tag is
# always taken from gacdi_manifest/__init__.py's __version__ (the single
# source of truth for versioning; pyproject.toml's version is derived from it
# via hatchling) and the job refuses to overwrite a tag that already exists on
# Quay, so a forgotten version bump fails the build instead of silently
# clobbering the published image. Requires repository secrets QUAY_USERNAME
# and QUAY_TOKEN (Quay robot account).
name: Build and push mcdi container

# Builds the mcdi image and pushes it to Quay whenever cli_tools/mcdi changes
# on main, or manually. The image tag is always taken from mcdi/__init__.py's
# __version__ (the single source of truth for versioning; pyproject.toml's
# version is derived from it via hatchling) and the job refuses to overwrite
# a tag that already exists on Quay, so a forgotten version bump fails the
# build instead of silently clobbering the published image. Requires
# repository secrets QUAY_USERNAME and QUAY_TOKEN (Quay robot account).
on:
push:
branches: ["main"]
paths:
- "cli_tools/gacdi_manifest/**"
- "cli_tools/mcdi/**"
workflow_dispatch:

env:
REGISTRY: quay.io
# Must match the <container> namespace in tools/manifest_gdc/macros.xml.
ORG: goeckslab
IMAGE: gacdi-manifest
IMAGE: mcdi

jobs:
manifest:
Expand All @@ -30,7 +29,7 @@ jobs:
- name: Read version from package __init__.py
id: version
run: |
VERSION=$(grep -m1 '^__version__' cli_tools/gacdi_manifest/gacdi_manifest/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
VERSION=$(grep -m1 '^__version__' cli_tools/mcdi/mcdi/__init__.py | sed -E 's/__version__ = "(.*)"/\1/')
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- uses: docker/setup-buildx-action@v3
Expand All @@ -46,16 +45,16 @@ jobs:
IMAGE_REF="${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.IMAGE }}:${{ steps.version.outputs.version }}"
if docker buildx imagetools inspect "$IMAGE_REF" >/dev/null 2>&1; then
echo "Tag ${{ steps.version.outputs.version }} already exists at $IMAGE_REF." >&2
echo "Bump __version__ in gacdi_manifest/__init__.py before pushing again." >&2
echo "Bump __version__ in mcdi/__init__.py before pushing again." >&2
exit 1
fi

- name: Build & push
uses: docker/build-push-action@v6
with:
context: cli_tools/gacdi_manifest
file: cli_tools/gacdi_manifest/Dockerfile
context: cli_tools/mcdi
file: cli_tools/mcdi/Dockerfile
push: true
build-args: |
GACDI_BUILD=${{ github.sha }}
MCDI_BUILD=${{ github.sha }}
tags: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.IMAGE }}:${{ steps.version.outputs.version }}
4 changes: 2 additions & 2 deletions .shed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ long_description: |
ready for gdc-client or the GaCDI GDC importer, together with an enriched metadata table that
joins sample barcodes and clinical/molecular annotations (GDC fields, cBioPortal subtypes, and
optional user-uploaded annotations) plus a match/precision report.
homepage_url: https://github.com/paulocilasjr/GaCDI
remote_repository_url: https://github.com/paulocilasjr/GaCDI
homepage_url: https://github.com/goeckslab/GaCDI
remote_repository_url: https://github.com/goeckslab/GaCDI
type: unrestricted
categories:
- Data Source
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
FROM python:3.12-alpine
WORKDIR /app
# build from gacdi parent directory
# build from the mcdi parent directory
COPY . .
RUN pip install --no-cache-dir .
# Build identifier (e.g. git SHA) so a run can report exactly which code it used.
ARG GACDI_BUILD=local
ENV GACDI_BUILD=${GACDI_BUILD}
CMD ["gacdi-manifest", "--help"]
ARG MCDI_BUILD=local
ENV MCDI_BUILD=${MCDI_BUILD}
CMD ["mcdi", "--help"]
File renamed without changes.
29 changes: 15 additions & 14 deletions cli_tools/gacdi_manifest/README.md → cli_tools/mcdi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

Galaxy Cancer Data Importers (GaCDI) provides Galaxy tools for importing cancer
datasets from major public and controlled-access cancer data repositories into
Galaxy histories. This package provides two commands: `gacdi-manifest` builds
a manifest from filters, and `gacdi-download` downloads the files a GDC or PDC
manifest lists (whether built here or exported from a portal).
Galaxy histories. This package provides one command, `mcdi` (Multi-Commons Data
Importer), with two subcommands: `mcdi manifest` builds a manifest from
filters, and `mcdi download` downloads the files a GDC or PDC manifest lists
(whether built here or exported from a portal).

## Manifest Builder (this branch)

Expand Down Expand Up @@ -33,11 +34,11 @@ table, which is joined back to the downloaded files by barcode afterwards.

```bash
# Preview how many files match before building
gacdi-manifest gdc --project TCGA-BRCA --data-type "Slide Image" --count-only \
mcdi manifest gdc --project TCGA-BRCA --data-type "Slide Image" --count-only \
--manifest-out m.txt --metadata-out meta.tsv --report-out report.tsv

# Build a slide-image manifest enriched with cBioPortal subtypes + a custom table
gacdi-manifest gdc \
mcdi manifest gdc \
--project TCGA-BRCA --data-type "Slide Image" --access open \
--cbioportal-study brca_tcga_pan_can_atlas_2018 \
--cbioportal-attrs SUBTYPE,ER_STATUS_BY_IHC,PR_STATUS_BY_IHC,HER2_STATUS \
Expand All @@ -46,7 +47,7 @@ gacdi-manifest gdc \
--manifest-out gdc_manifest.txt --metadata-out metadata.tsv --report-out report.tsv

# Discover a study's cBioPortal attribute ids
gacdi-manifest gdc --cbioportal-study brca_tcga_pan_can_atlas_2018 \
mcdi manifest gdc --cbioportal-study brca_tcga_pan_can_atlas_2018 \
--cbioportal-list-attrs --manifest-out m.txt --metadata-out meta.tsv --report-out report.tsv
```

Expand Down Expand Up @@ -88,8 +89,8 @@ annotations (e.g. labels for an image ML model).

## Downloading files from a manifest

`gacdi-download` fetches the files listed in a GDC or PDC manifest — either
one built by `gacdi-manifest gdc` above, or one exported directly from a
`mcdi download` fetches the files listed in a GDC or PDC manifest — either
one built by `mcdi manifest gdc` above, or one exported directly from a
portal:

- **GDC**: build a file cart in the [GDC portal](https://portal.gdc.cancer.gov)
Expand All @@ -101,8 +102,8 @@ portal:
re-export if downloads start failing.

```bash
gacdi-download --manifest gdc_manifest.txt --output-dir downloads/
gacdi-download --manifest pdc_manifest.csv --output-dir downloads/ --verify-checksum
mcdi download --manifest gdc_manifest.txt --output-dir downloads/
mcdi download --manifest pdc_manifest.csv --output-dir downloads/ --verify-checksum
```

The data commons is auto-detected from the manifest's header row; pass
Expand All @@ -123,7 +124,7 @@ environment variable or `--token-file`:

```bash
export GDC_TOKEN="$(cat gdc-user-token.txt)"
gacdi-download --manifest gdc_manifest.txt --output-dir downloads/
mcdi download --manifest gdc_manifest.txt --output-dir downloads/
```

PDC downloads use pre-signed URLs embedded in the manifest and need no token.
Expand All @@ -139,15 +140,15 @@ interrupted runs can simply be re-run.

## Runtime environment

The tool ships a pinned container (`quay.io/<org>/gacdi-manifest`) referenced from
The tool ships a pinned container (`quay.io/<org>/mcdi`) referenced from
the wrapper, with Python + `requests` Conda requirements as a fallback. The Quay
namespace (`paulocilasjr`) is a placeholder — update `@QUAY_ORG@` in
`tools/manifest_gdc/macros.xml`, `containers/Dockerfile.manifest`, and the workflow
before publishing.

```bash
docker build -f containers/Dockerfile.manifest -t gacdi-manifest:dev .
docker run --rm gacdi-manifest:dev gacdi-manifest gdc --help
docker build -f containers/Dockerfile.manifest -t mcdi:dev .
docker run --rm mcdi:dev mcdi manifest gdc --help
```

## Development
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
"""GaCDI Manifest Builder + Downloader.
"""MCDI — Multi-Commons Data Importer.

Two commands sharing one package:
One command, two subcommands:

- ``gacdi-manifest`` (:mod:`gacdi_manifest.manifest`): filter-driven generation of
- ``mcdi manifest`` (:mod:`mcdi.manifest`): filter-driven generation of
download manifests (and enriched metadata tables) for NIH/NCI cancer data
repositories, starting with the NCI Genomic Data Commons (GDC). The builder
emits a deliberate *two-file split*: a lean, CLI/importer-ready manifest
(``id/filename/md5/size/state``) and a rich metadata table joining
clinical/molecular annotations by barcode, plus a match report so selections
and joins are never silently wrong.
- ``gacdi-download`` (:mod:`gacdi_manifest.download`): downloads the files listed
- ``mcdi download`` (:mod:`mcdi.download`): downloads the files listed
in a GDC or PDC manifest, auto-detecting which commons it came from.
"""

import os

__version__ = "0.2.0"
__version__ = "0.3.0"

# Build identifier baked into the container image at build time (e.g. the git
# commit SHA). Lets you confirm the exact code a run used, even when the version
# number hasn't changed. Empty for local/editable installs.
BUILD = os.environ.get("GACDI_BUILD", "").strip()
BUILD = os.environ.get("MCDI_BUILD", "").strip()


def version_string() -> str:
Expand Down
42 changes: 42 additions & 0 deletions cli_tools/mcdi/mcdi/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""``mcdi`` — Multi-Commons Data Importer: single entry point for the ``manifest`` and ``download`` subcommands."""

from __future__ import annotations

import argparse
import logging
import sys

from . import version_string
from .download import cli as download_cli
from .errors import ManifestError
from .manifest import cli as manifest_cli

log = logging.getLogger("mcdi")


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="mcdi", description="MCDI: Multi-Commons Data Importer.")
parser.add_argument("--version", action="version", version=f"mcdi {version_string()}")
sub = parser.add_subparsers(dest="command", required=True, metavar="COMMAND")
manifest_cli.add_arguments(sub)
download_cli.add_arguments(sub)
return parser


def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
logging.basicConfig(
level=logging.DEBUG if getattr(args, "verbose", False) else logging.INFO,
format="%(levelname)s %(name)s: %(message)s",
)
# Emit the running version to the job log so it is visible in Galaxy's job info.
log.info("mcdi %s", version_string())
try:
return args.func(args)
except ManifestError as exc:
log.error("%s", exc)
return exc.exit_code


if __name__ == "__main__": # pragma: no cover
sys.exit(main())
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
"""CLI: ``gacdi-download`` — download the files listed in a GDC or PDC manifest."""
"""``mcdi download`` — download the files listed in a GDC or PDC manifest."""

from __future__ import annotations

import argparse
import logging
import sys
from pathlib import Path

from .. import version_string
from ..errors import InputError, ManifestError
from ..errors import InputError
from . import config, engine
from .sources import SOURCES, detect_source

log = logging.getLogger("gacdi_manifest.download")
log = logging.getLogger("mcdi.download")


def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="gacdi-download",
description="Download files listed in a GDC or PDC manifest.",
def add_arguments(subparsers: argparse._SubParsersAction) -> argparse.ArgumentParser:
"""Attach the ``download`` subcommand to ``subparsers``."""
parser = subparsers.add_parser(
"download",
help="Download files listed in a GDC or PDC manifest.",
)
parser.add_argument("--version", action="version", version=f"gacdi-download {version_string()}")
parser.add_argument("--manifest", required=True, type=Path, help="Path to the exported manifest file")
parser.add_argument("--output-dir", required=True, type=Path, help="Directory to download files into")
parser.add_argument(
Expand All @@ -39,10 +37,11 @@ def build_parser() -> argparse.ArgumentParser:
help="Path to a file containing a GDC auth token (overrides GDC_TOKEN env var)",
)
parser.add_argument("--verbose", action="store_true")
parser.set_defaults(func=run)
return parser


def _run(args: argparse.Namespace) -> int:
def run(args: argparse.Namespace) -> int:
if not args.manifest.is_file():
raise InputError(f"manifest not found: {args.manifest}")

Expand Down Expand Up @@ -76,21 +75,3 @@ def _run(args: argparse.Namespace) -> int:
f"{len(mismatches)} checksum mismatch(es)"
)
return 1 if failed or mismatches else 0


def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
logging.basicConfig(
level=logging.DEBUG if args.verbose else logging.INFO,
format="%(levelname)s %(name)s: %(message)s",
)
log.info("gacdi-download %s", version_string())
try:
return _run(args)
except ManifestError as exc:
log.error("%s", exc)
return exc.exit_code


if __name__ == "__main__": # pragma: no cover
sys.exit(main())
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def build_session() -> requests.Session:
retries=RETRY_TOTAL,
backoff=RETRY_BACKOFF,
allowed_methods=frozenset({"GET", "HEAD"}),
user_agent=f"gacdi-manifest/{version_string()}",
user_agent=f"mcdi/{version_string()}",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from ..errors import ApiError

log = logging.getLogger("gacdi_manifest.manifest.cbioportal")
log = logging.getLogger("mcdi.manifest.cbioportal")

DEFAULT_BASE = "https://www.cbioportal.org/api"

Expand Down
Loading
Loading