Skip to content

feat(ucsc): add gget ucsc module to fetch UCSC IDs (#18)#233

Open
Elarwei001 wants to merge 5 commits into
scverse:devfrom
Elarwei001:feature/ucsc-18
Open

feat(ucsc): add gget ucsc module to fetch UCSC IDs (#18)#233
Elarwei001 wants to merge 5 commits into
scverse:devfrom
Elarwei001:feature/ucsc-18

Conversation

@Elarwei001

@Elarwei001 Elarwei001 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Resolves #18

Summary

gget ucsc: new module to fetch UCSC Genome Browser IDs for a gene or term — the UCSC counterpart of gget search (Ensembl). It queries UCSC's official, documented /search REST endpoint and returns the matching identifiers (UCSC known-gene / transcript IDs, HGNC IDs, …) together with their genomic positions, grouped by the track they come from. Available in the Python API and on the command line.

What it returns

One row per match, flattened from UCSC's positionMatches and tagged with the track it came from (snippet — first 3 rows):

>>> gget.ucsc("BRCA2", genome="hg38", limit=3)
    track            ucsc_id  chrom     start       end                                     name                                   description
     mane  ENST00000380152.8  chr13  32315508  32400268  BRCA2 (NM_000059.4 / ENST00000380152.8)  breast cancer type 2 susceptibility protein
     hgnc          HGNC:1101  chr13  32315086  32400268                                    BRCA2                  BRCA2 DNA repair associated
knownGene  ENST00000380152.8  chr13  32315508  32400268                BRCA2 (ENST00000380152.8)  BRCA2 DNA repair associated transcript … 

With json=True (the command-line default) the same rows come back as records:

[
  {
    "track": "mane",
    "ucsc_id": "ENST00000380152.8",
    "chrom": "chr13",
    "start": 32315508,
    "end": 32400268,
    "name": "BRCA2 (NM_000059.4 / ENST00000380152.8)",
    "description": "breast cancer type 2 susceptibility protein"
  }
]

Options: genome (default hg38), track (case-insensitive substring filter), limit, plus json / save. URL-encoded IDs (HGNC%3A1101HGNC:1101) and HTML entities in labels are decoded. Because /search is a text search, a broad term can also surface related genes (e.g. BRCA2 also matches BCCIP / BRCC3, whose descriptions mention BRCA2), so a common gene returns many rows across many tracks — narrow with track / limit.

Full example outputgget.ucsc("BRCA2", genome="hg38") returns 641 rows across 45 tracks; the complete result (CSV + JSON) is attached here: https://gist.github.com/Elarwei001/0879c9c75f6bb7f87d60c81111f929cb

What is a "track group"?

UCSC stacks every kind of annotation over a genomic region as a horizontal track (gene models, gene naming, RefSeq, GENCODE, variants, …). A search returns its matches grouped by track (positionMatches), and gget ucsc flattens each group's matches into rows tagged with their track. You can see these tracks visually here — BRCA2 in hg38:

https://genome.ucsc.edu/cgi-bin/hgTracks?db=hg38&position=chr13:32315508-32400268

Each stacked row in that view corresponds to a track group in the API response. A common gene like BRCA2 hits dozens of tracks (~46), which is why track / limit are provided to narrow the result.

Testing

Unit tests in tests/test_ucsc.py (with fixtures in tests/fixtures/) cover: position parsing, the URL-encoded-ID and HTML-entity quirks, the group-description fallback (including the majority of tracks that omit a match-level description), the track filter, limit / json / save, and the error paths (empty term, request failure, and a bad genome that now surfaces UCSC's own error message rather than a generic one).

Two live tests (skipped when the network is unavailable, so CI stays green) hit the real UCSC /search API: a truth-anchored BRCA2 check (chr13, ENST00000380152), and an unfiltered search that exercises the parser across every real track type — not just the gene-model tracks.

@codecov-commenter

codecov-commenter commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.20%. Comparing base (e43a804) to head (1c90595).
⚠️ Report is 2 commits behind head on dev.

Files with missing lines Patch % Lines
gget/gget_ucsc.py 96.20% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #233      +/-   ##
==========================================
+ Coverage   56.70%   57.20%   +0.49%     
==========================================
  Files          29       30       +1     
  Lines        9392     9473      +81     
==========================================
+ Hits         5326     5419      +93     
+ Misses       4066     4054      -12     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Elarwei001 Elarwei001 marked this pull request as draft June 25, 2026 03:44
@lauraluebbert lauraluebbert deleted the branch scverse:dev June 28, 2026 20:31
@lauraluebbert lauraluebbert reopened this Jun 28, 2026
Elarwei001 and others added 2 commits June 28, 2026 19:51
New module `gget ucsc` searches the UCSC Genome Browser REST API for a
gene symbol, accession, or term and returns the matching UCSC identifiers
(e.g. known gene / transcript IDs) with their genomic positions, grouped
by track — analogous to gget search for Ensembl. Supports filtering by
genome, track, and limit. Exposed via the Python API and the command line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add network-free mocked tests for empty search_term, _parse_position
no-range branch, verbose logging, request-exception handling, and the
save CSV/JSON branches. gget_ucsc.py now 100%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Elarwei001 and others added 3 commits July 8, 2026 22:06
…se#18)

- docs: put the UCSC (Kent 2002) citation before gget's, per convention
- docs: drop the issue link from the updates.md changelog entry
- docs: add Spanish page (es/ucsc.md) + SUMMARY Espanol entry
- gget_ucsc: surface UCSC's own error message on 4xx (e.g. bad genome)
  instead of a generic 'try again later'; add a non-JSON guard
- tests: add a bad-genome (HTTP 400) test and a truth-anchored live
  test against the real UCSC /search API (skipped when offline)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e search (scverse#18)

- add a mocked case for the majority track shape (no match-level
  'description' key + an 'extraSel' field) exercising the group-description
  fallback
- add a live test that runs an unfiltered BRCA2 search so the parser is
  exercised across every real UCSC track type, not just gene-model tracks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/--limit (scverse#18)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Elarwei001

Copy link
Copy Markdown
Contributor Author

Note on the red CI: the only failing test is tests/test_cellxgene.py::TestCellxgene::test_cellxgene_adata, which is unrelated to this PR — this PR only touches gget ucsc + docs, and all gget ucsc tests (including the live ones) pass (439 passed, 1 failed = the cellxgene test, identically on py3.12/3.13/3.14).

It's an upstream live-data drift: cellxgene-census now returns an AnnData whose .layers contains a None-keyed entry, so the exact-dict comparison against the stored fixture fails. dev's last CI run (2026-06-28) was green, so it appeared since then. Tracked separately in #265.

@Elarwei001

Copy link
Copy Markdown
Contributor Author

Hi @lauraluebbert — this PR is ready for review whenever you have time. Quick overview:

  1. What it adds — gget ucsc, the UCSC counterpart of gget search: given a gene symbol, accession, or free-text term, it returns the matching UCSC identifiers and their genomic positions. Resolves Add feature to fetch UCSC IDs  #18.

  2. How it works — the module calls UCSC's documented /search endpoint and turns its positionMatches response into a tidy table: one row per match, each tagged with the track it came from. A gene like BRCA2 therefore comes back as hits spread across many tracks — knownGene, RefSeq, GENCODE, HGNC, MANE, and so on. The result is a DataFrame (or JSON) that can be narrowed with genome (default hg38), a case-insensitive track substring filter, and limit; along the way the module decodes UCSC's URL-encoded IDs and the HTML entities in their labels. For context, the PR description walks through the "track group" idea and links a live browser view.

  3. Testing — unit tests cover the parsing, the two real quirks (URL-encoded IDs, HTML entities), the group-description fallback (including the many tracks that omit a match-level description), filtering, and the error paths (empty term, request failure, and a bad genome that surfaces UCSC's own error). Two live tests (skipped when offline) hit the real UCSC API: a truth-anchored BRCA2 check and an unfiltered search that exercises every real track type.

Happy to adjust anything — thanks!

@Elarwei001 Elarwei001 marked this pull request as ready for review July 8, 2026 15:20
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.

3 participants