Skip to content

Modernize - #1

Merged
tanghaibao merged 4 commits into
masterfrom
modernize
Apr 17, 2026
Merged

tanghaibao merged 4 commits into
masterfrom
modernize

Conversation

@tanghaibao

Copy link
Copy Markdown
Owner

No description provided.

tanghaibao and others added 4 commits April 16, 2026 18:03
- Replace Py2-only constructs (print statements, fp.next, basestring,
  df.ix, df.reindex_axis) with Py3 equivalents
- Swap os.system shell invocation for subprocess.run with a list of
  args, avoiding shell injection via bam/samplekey paths
- Point datadir/exec_path at src/data and src/Splithunter so the
  package finds the compiled binary and bundled indices
- Simplify get_abs_path via os.path.realpath (fixes pathological
  recursion) and drop unused code (InputParams, Popen/popen, sh,
  s3 helpers)
- Rewrite tests/test_splithunter.py with real assertions, tmp_path
  isolation, and a skip guard when the C++ binary is absent
- Drop legacy top-level wrappers now exposed as console scripts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace setup.py + setup_helper.py with a pyproject.toml declaring
  Python 3.8+, BSD-3-Clause license, and console_scripts entry points
  for splithunter_run / splithunter_report
- Swap retired Travis config for a GitHub Actions workflow testing
  Python 3.9 and 3.11 with submodule checkout
- Add a root .gitignore covering Python build artifacts, compiled
  C++ outputs, and splithunter work directories
- Upgrade the Dockerfile to Ubuntu 22.04, Python 3, pinned apt
  dependencies with cleanup, and the new console-script entry point
- Refresh README with CI badge, updated email/license, and Py3 install
  instructions
- Drop one-line helper scripts (update/rm submodules) that belong in
  README documentation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tanghaibao/SeqLib fork is gone, so recursive checkout aborted the
workflow. The Python test suite already skips when the Splithunter C++
binary is absent, so running CI without the submodule still validates
the Python side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pyproject.toml console_scripts wrappers call main() with no
arguments; argparse then parses sys.argv on its own. Caught by running
splithunter_run --help after pip install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 17, 2026 01:18
@tanghaibao
tanghaibao merged commit dd6eb45 into master Apr 17, 2026
6 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes the project by migrating to Python 3 + PEP 517/518 packaging, refreshing the CLI entry points and CI, and cleaning out legacy scripts/config.

Changes:

  • Replace setup.py/Travis-era packaging with pyproject.toml and console scripts (splithunter_run, splithunter_report).
  • Update core modules for Python 3 compatibility and safer subprocess/file handling.
  • Add GitHub Actions CI and new pytest-based tests; update Dockerfile and README accordingly.

Reviewed changes

Copilot reviewed 18 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
update_submodules.sh Removed legacy helper script.
tests/test_splithunter.py New pytest suite (currently binary-dependent).
tests.py Removed old test runner.
splithunter_run.py Removed legacy script wrapper in favor of console entry point.
splithunter_report.py Removed legacy script wrapper in favor of console entry point.
splithunter/utils.py Python 3 shebang + simplification/removal of unused utilities.
splithunter/run.py Modernized runner (paths, subprocess usage, multiprocessing, argparse).
splithunter/report.py Modernized report generation and pandas usage.
splithunter/init.py Updated metadata (email/license/version).
setup_helper.py Removed legacy setup helper.
setup.py Removed legacy setuptools entry.
rm_submodules.sh Removed legacy helper script.
pyproject.toml New build metadata, dependencies, and console scripts.
docker/splithunter.dockerfile Updated base image + Python 3 install/build flow.
docker/Makefile Updated test run command to new CLI.
README.md Updated CI badge, install and usage instructions, dev workflow.
.travis.yml Removed Travis CI config.
.gitignore Added modern Python/C++ build artifacts and run outputs.
.github/workflows/ci.yml Added GitHub Actions CI workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread splithunter/run.py
Comment on lines +32 to +33
DATA_DIR = get_abs_path(op.join(SRC_DIR, "data"))
HLI_BAMS = op.join(DATA_DIR, "HLI_bams.csv.gz")

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

HLI_BAMS is constructed as SRC_DIR/data/HLI_bams.csv.gz, but the repository’s HLI_bams file is under splithunter/data/HLI_bams.csv.gz (and src/data/ does not contain it). This will raise a FileNotFoundError when using the @<samplekey> input mode. Consider resolving HLI_bams.csv.gz from package data (e.g., via importlib.resources) and/or falling back to src/data if present.

Suggested change
DATA_DIR = get_abs_path(op.join(SRC_DIR, "data"))
HLI_BAMS = op.join(DATA_DIR, "HLI_bams.csv.gz")
def resolve_hli_bams():
package_data_dir = get_abs_path(op.join(PACKAGE_ROOT, "data"))
legacy_data_dir = get_abs_path(op.join(SRC_DIR, "data"))
for data_dir in (package_data_dir, legacy_data_dir):
hli_bams = op.join(data_dir, "HLI_bams.csv.gz")
if op.exists(hli_bams):
return hli_bams
return op.join(package_data_dir, "HLI_bams.csv.gz")
HLI_BAMS = resolve_hli_bams()

Copilot uses AI. Check for mistakes.
Comment thread splithunter/run.py
Comment on lines +162 to +193
# Mode 1: single BAM/CRAM file
if csvfile.endswith((".bam", ".cram")):
bam = bam_path(csvfile)
if args.workflow_execution_id and args.sample_id:
samplekey = "_".join((args.workflow_execution_id, args.sample_id))
else:
samplekey = op.basename(bam).rsplit(".", 1)[0]
return [(samplekey, bam)]

fp = open(csvfile)
# Mode 2: See if the file contains JUST list of BAM files
header = fp.next().strip()
contents = []
if header.endswith(".bam") and header.count(",") == 0:
with open(csvfile) as fp:
header = fp.readline().strip()

# Mode 2: just a list of BAM files
if header.endswith(".bam") and header.count(",") == 0:
fp.seek(0)
for row in fp:
bam = bam_path(row.strip())
samplekey = op.basename(bam).rsplit(".", 1)[0]
contents.append((samplekey, bam))
return contents

# Mode 3: CSV with samplekey,bam
fp.seek(0)
for row in fp:
bam = row.strip()
atoms = row.strip().split(",")
if len(atoms) < 2:
continue
samplekey, bam = atoms[:2]
bam = bam_path(bam)
samplekey = op.basename(bam).rsplit(".", 1)[0]
contents.append((samplekey, bam))
return contents

# Mode 3: Continue reading, this is a CSV file
fp.seek(0)
for row in fp:
atoms = row.strip().split(",")
samplekey, bam = atoms[:2]
bam = bam_path(bam)
if bam.endswith(".bam"):
contents.append((samplekey, bam))
if bam.endswith(".bam"):
contents.append((samplekey, bam))

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

read_csv() supports a single .cram input (Mode 1), but Mode 2 (list file) only detects .bam headers and Mode 3 only appends rows where the BAM path ends with .bam. This makes list/CSV inputs containing CRAMs silently produce no samples. Consider treating .cram the same as .bam in these modes (both for header detection and row filtering).

Copilot uses AI. Check for mistakes.
Comment thread splithunter/run.py
task_args.append((samplekey, bam, args))

cpus = min(args.cpus, len(task_args))
cpus = min(args.cpus, len(task_args)) if task_args else 0

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

--cpus can be set to 0 (or a negative number). In that case cpus = min(args.cpus, len(task_args)) becomes 0 even when there is pending work, causing the run to log "All jobs already completed" and skip execution. Consider validating args.cpus >= 1 (argparse type/custom check) and/or coercing to at least 1 before computing the pool size.

Suggested change
cpus = min(args.cpus, len(task_args)) if task_args else 0
if task_args:
cpus = min(max(1, args.cpus), len(task_args))
else:
cpus = 0

Copilot uses AI. Check for mistakes.
Comment thread splithunter/report.py
Comment on lines 123 to 128
nfiles = len(files)
cpus = min(nfiles, args.cpus)
suffix = "JSON"
print >> sys.stderr, "Using {} cpus to parse {} {} files"\
.format(cpus, nfiles, suffix)
df = json_to_df(files, tsvfile, cpus)
print("Using {} cpus to parse {} JSON files".format(cpus, nfiles),
file=sys.stderr)
df = json_to_df(files, cpus)
df_to_tsv(df, tsvfile)

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

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

If a user passes --cpus 0, cpus = min(nfiles, args.cpus) becomes 0 and Pool(processes=cpus) will raise a ValueError. Consider validating args.cpus >= 1 (and/or clamping cpus to at least 1 when files is non-empty).

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml
Comment on lines +23 to +33
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

- name: Syntax check
run: |
python -m compileall -q splithunter

- name: Run unit tests
run: pytest tests/ -v

Copilot AI Apr 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 unit tests are marked skipif when the C++ Splithunter binary is not built, but this workflow never builds it (no make -C src). As a result, pytest will typically skip the new tests and CI won’t exercise the core functionality. Consider adding a step to initialize submodules and build the binary (and any required system deps) before running pytest, or adjust the tests to cover pure-Python behavior that can run in CI without the binary.

Copilot uses AI. Check for mistakes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dd706d8d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml

on:
push:
branches: [master, modernize]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include main branch in CI push triggers

The workflow only runs on pushes to master and modernize, so pushes to the repository’s active main branch won’t execute CI at all. This creates a blind spot where regressions can be merged without any automated checks; adding main to the push branch filter restores expected coverage.

Useful? React with 👍 / 👎.

Comment thread .github/workflows/ci.yml
Comment on lines +23 to +26
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Build Splithunter binary before running test suite

The workflow installs Python dependencies and runs pytest, but it never builds the C++ Splithunter executable first. In this commit the tests are guarded by skipif when that binary is missing, so CI can pass while skipping the core end-to-end tests entirely; adding a cd src && make step (or equivalent) before pytest is needed to actually exercise runtime behavior.

Useful? React with 👍 / 👎.

@tanghaibao
tanghaibao deleted the modernize branch April 17, 2026 01:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants