Skip to content
Draft
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
2 changes: 2 additions & 0 deletions docs/src/en/updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#### *gget* officially became part of [*scverse*](https://scverse.org/) on June 9, 2026. 🥳🥳🥳

**Version ≥ 0.30.9** (XXX XX, 2026):
- Bug fixes:
- [`gget seq`](seq.md): Transcript/ENST IDs now return the spliced cDNA sequence instead of the genomic span (which included introns). gget requests `type=cdna` from the Ensembl `sequence/id` endpoint for transcript IDs across all code paths (bulk, isoform, and single-ID); gene IDs are unaffected and still return the genomic sequence. Resolves [issue 187](https://github.com/scverse/gget/issues/187).

**Version ≥ 0.30.8** (Jun 28, 2026):
- [`gget g2p`](g2p.md): Either `gene` or `--uniprot_id` is now sufficient — whichever is missing is resolved via UniProt and cached. Gene→UniProt picks the canonical reviewed human Swiss-Prot entry; the resolution and its limitations are logged. The canonical pair is **always** prepended to the result as `gene_name` / `uniprot_id` columns (and stored on `df.attrs`), so the output schema is invariant regardless of input mode. Existing call sites continue to work.
Expand Down
51 changes: 41 additions & 10 deletions gget/gget_seq.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,34 @@ def seq(
if not isoforms:
actual_results_dict = {}

endpoint = "sequence/id/"
query = {"ids": ens_ids_clean}
# Ensembl's sequence/id endpoint returns the *genomic* sequence by
# default. For a transcript, that genomic span includes the introns
# rather than the spliced transcript, so we must request type=cdna
# for transcripts (issue #187). Genes keep the default genomic
# sequence. Classify the IDs with a single bulk lookup and split the
# request into a cDNA batch (transcripts) and a genomic batch (rest).
try:
id_types = post_query(server, "lookup/id", {"ids": ens_ids_clean})
except RuntimeError:
# If the lookup fails, fall back to the previous behaviour
# (single genomic request) rather than erroring out.
id_types = {}

transcript_ids = []
other_ids = []
for ensembl_ID in ens_ids_clean:
lookup_entry = id_types.get(ensembl_ID) if isinstance(id_types, dict) else None
if isinstance(lookup_entry, dict) and lookup_entry.get("object_type") == "Transcript":
transcript_ids.append(ensembl_ID)
else:
other_ids.append(ensembl_ID)

results_list = []
if transcript_ids:
results_list += post_query(server, "sequence/id?type=cdna", {"ids": transcript_ids})
if other_ids:
results_list += post_query(server, "sequence/id", {"ids": other_ids})

# noinspection PyTypeChecker
results_list = post_query(server, endpoint, query)
results_dict = {v["query"]: v for v in results_list if v is not None}

for ensembl_ID, df_temp in results_dict.items():
Expand Down Expand Up @@ -149,8 +172,9 @@ def seq(

# Try if query is valid
try:
# Define the REST query
query = "sequence/id/" + transcipt_id + "?"
# Define the REST query (request the spliced cDNA, not
# the genomic span, for transcripts -- issue #187)
query = "sequence/id/" + transcipt_id + "?type=cdna"
# Submit query
df_temp = rest_query(server, query, content_type)

Expand All @@ -172,8 +196,11 @@ def seq(
else:
# Try if query is valid
try:
# Define the REST query
query = "sequence/id/" + ensembl_ID + "?"
# Define the REST query. Request the spliced cDNA for
# transcripts (issue #187); any other object type keeps
# the default genomic sequence.
seq_type = "cdna" if ens_ID_type == "Transcript" else "genomic"
query = "sequence/id/" + ensembl_ID + "?type=" + seq_type

# Submit query
df_temp = rest_query(server, query, content_type)
Expand Down Expand Up @@ -201,11 +228,15 @@ def seq(
# Build FASTA file
for ens_ID in master_dict:
for key in master_dict[ens_ID].keys():
# The cDNA (type=cdna) response has no "desc" field (None); coerce
# to an empty string so the FASTA header still builds.
if key == "seq":
fasta.append(">" + ens_ID + " " + master_dict[ens_ID][key]["desc"])
desc = master_dict[ens_ID][key].get("desc") or ""
fasta.append((">" + ens_ID + " " + desc).rstrip())
fasta.append(master_dict[ens_ID][key]["seq"])
else:
fasta.append(">" + master_dict[ens_ID][key]["id"] + " " + master_dict[ens_ID][key]["desc"])
desc = master_dict[ens_ID][key].get("desc") or ""
fasta.append((">" + master_dict[ens_ID][key]["id"] + " " + desc).rstrip())
fasta.append(master_dict[ens_ID][key]["seq"])

## Fetch amino acid sequences from UniProt
Expand Down
Loading
Loading