Skip to content

Rewrite CANDy as an installable Python package (CLI + API) - #2

Merged
AlexWindels merged 7 commits into
mainfrom
package-upgrade
Aug 7, 2026
Merged

AlexWindels merged 7 commits into
mainfrom
package-upgrade

Conversation

@AlexWindels

@AlexWindels AlexWindels commented Aug 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Rewrites CANDy from a Google Colab / Jupyter notebook into an installable Python package (candy-cazyme) with both a CLI (candy GH173) and a Python API, faithfully porting the original 13-stage pipeline while fixing several real bugs found via live testing against CAZy/NCBI/InterPro.
  • Bundles its clustering/MSA/phylogenetics toolchain (MMseqs2 auto-download, FAMSA and VeryFastTree as pip dependencies) so pip install candy-cazyme works with minimal external setup on Windows, macOS, and Linux; the original CD-HIT/MAFFT/FastTree conda path is still available as an opt-in alternative.
  • Adds a pluggable domain-name curation step (manual interactive prompt, or automated via Gemini).
  • Adds a full pytest suite (106 tests), CI (GitHub Actions) to run it on every push/PR, and a release workflow that publishes to PyPI via trusted publishing when a GitHub Release is cut.
  • Archives the original notebook under archive/.

Test plan

  • pytest — 106 passed
  • Built sdist + wheel with python -m build, installed the wheel into a clean, isolated venv, confirmed candy --help and the entry point work with no leftover dev-environment dependencies
  • Manually ran the full pipeline end-to-end against live CAZy/NCBI/InterPro/MMseqs2/Gemini for a real family (GH173) on Windows, including --tree and Gemini-based curation
  • Reviewer: confirm CI passes on this PR (tests.yml) before merging

Alex Windels and others added 7 commits August 6, 2026 18:34
Reimplements all 13 stages of the Colab notebook pipeline as a proper
pip-installable package (src/candy) instead of Colab-only cells, with
pluggable clustering (CD-HIT/MMseqs2), alignment (MAFFT), phylogenetics
(FastTree), and domain-curation (manual/Gemini) backends so future tool
swaps don't require another rewrite. Adds a Typer CLI (`candy run`), a
SQLAlchemy-backed result database, and 79 tests covering the pure-logic
modules plus mocked end-to-end pipeline runs for both input modes.

While porting, several real bugs in the original notebook were found and
fixed (each documented in-code and covered by a regression test):
duplicated taxonomy codes corrupting characterized-enzyme organism names,
a BLAST-fallback crash from state leaking across loop iterations, swapped
taxonomy/organism_name columns in the SQLite schema, and a value-based
list.index() bug mis-pairing domain names to positions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nstall

Replaces the conda-only external-tool requirement with a "pip install and
run" experience: FAMSA (via pyfamsa) and VeryFastTree (via veryfasttree)
are now real pip dependencies with bundled binaries, and MMseqs2 is
auto-downloaded and cached from the official GitHub release on first
clustering run. CD-HIT/MAFFT/FastTree remain available as opt-in
alternatives via the (now much smaller) conda environment for anyone who
wants the original notebook's exact toolchain.

Testing this for real against live binaries (not mocks) surfaced two
upstream bugs, both fixed with regression tests: veryfasttree's default
ext="AUTO" crashes with an access violation on Windows/AVX2 machines
(now always passes an explicit ext matching the bundled binary), and
MMseqs2's Windows build needs its mmseqs.bat wrapper (not mmseqs.exe
directly) since its clustering workflows are internally POSIX shell
scripts -- documented clearly since first use may prompt for
administrator permission once, mirroring MMseqs2's own upstream guidance.

Also simplifies the CLI to a single positional target (`candy GH173` /
`candy sequences.fasta`, auto-detected, no `run` subcommand needed), adds
CANDY_EMAIL env var support, restores jobname-collision avoidance that
had been dropped from the original notebook port, and adds a
--db-preference flag to reprioritize InterPro member databases without
needing to spell out the full default ranking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manually running CANDy end-to-end against a live CAZy family (GH173)
surfaced two distinct MMseqs2 failures on Windows, both fixed with
regression tests and verified against a real 155-sequence clustering run:

- Clustering silently produced no output when the project path contained
  a space (e.g. under "...\OneDrive - Org\..."): MMseqs2's Windows build
  routes its clustering workflow through a bundled Cygwin/busybox POSIX
  shell layer, which mishandles such paths. Clustering now always runs
  from a space-free temp directory with relative filenames.
- easy-cluster's internal FASTA-conversion step (result2flat) segfaulted
  through that same Cygwin process-spawning layer on real data, even
  though every preceding clustering step succeeded. Replaced the
  easy-cluster convenience workflow with its constituent steps run
  individually (createdb -> cluster -> result2repseq -> convert2fasta),
  using the shell-capable mmseqs.bat only for the one step that actually
  needs a POSIX shell (cluster's cascaded-clustering algorithm) and the
  plain compiled binary directly for the rest.
- mmseqs.bat always exits 0 regardless of internal failure, so success is
  now verified by checking the expected output file actually exists
  rather than trusting the return code.

Also broadens NCBI/CAZy fetch retry logic beyond urllib.error.HTTPError
to cover the wider class of transient connection failures (a real run
hit http.client.IncompleteRead mid-fetch on a large batched Entrez
request, which the narrower except-clause didn't catch).

Documents the interactive domain-name curation flow and how to switch to
Gemini-based curation with your own API key, and prints a citation
reminder (Windels et al., PLoS One 2024) at the start of every pipeline
run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The hardcoded gemini-2.0-flash default lost its free-tier quota, and the
next hardcoded guess (gemini-2.5-flash) turned out to be restricted for
new users too -- discovered via a real run, not just docs. Switch to
Google's gemini-flash-latest alias, which tracks whatever their current
free-tier flash model is, and add a --curation-model flag so a future
rename/deprecation doesn't need a code change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add top-level LICENSE (previously only embedded in SECURITY.md, which
  GitHub/PyPI license detection doesn't look at)
- Add project.urls and bump version off the 3.0.0.dev0 pre-release marker
- Add CI: run pytest on every push/PR, and publish to PyPI on GitHub
  Release via trusted publishing (OIDC, no stored API token)
- Archive the original notebook under archive/, update README to reflect
  the package as the primary distribution rather than a WIP branch

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fetch_family_page previously retried a 404 (nonexistent family) up to 5
times against the same permanent failure before surfacing a raw
requests.HTTPError traceback. Now it raises a clear ValueError
immediately, and the CLI catches ValueError from run_pipeline to print
a one-line "Error: ..." message and exit non-zero instead of a full
traceback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
test_cli_bare_invocation_is_candy_target_no_subcommand_needed failed on
every CI matrix job (but passed locally) because Typer's Rich-rendered
error panel is colorized there, and the ANSI escape codes happened to
land mid-phrase at a wrapped line boundary, breaking a substring check
that only stripped box-drawing characters, not color codes. Add an
ANSI-stripping helper and use it for all Rich-panel-derived assertions;
verified locally by forcing the same colorized rendering with
FORCE_COLOR=1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@AlexWindels
AlexWindels merged commit ef50a9a into main Aug 7, 2026
6 checks passed
@AlexWindels
AlexWindels deleted the package-upgrade branch August 7, 2026 11:59
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.

1 participant