scripts/quickgo_tool.py
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A command-line tool for querying the QuickGO API.
This script provides subcommands to interact with various QuickGO API endpoints,
including searching for GO terms, ECO terms, annotations, and gene products.
Results are saved to a specified JSON file.
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "polite-http",
# ]
# ///
import argparse
import json
from typing import Any
import urllib.parse
from polite_http import http_client
BASE_URL = "https://www.ebi.ac.uk/QuickGO/services"
_CLIENT = http_client.HttpClient(BASE_URL, qps=10)
def make_request(path: str, params: dict[str, Any] | None = None) -> Any:
"""Makes a GET request to the QuickGO API.
Args:
path: The API endpoint path (e.g., "/ontology/go/search").
params: Optional dictionary of query parameters.
Returns:
The JSON response from the API.
"""
url = f"{BASE_URL}{path}"
if params:
url = f"{url}?{urllib.parse.urlencode(params, doseq=True)}"
return _CLIENT.fetch_json(url)
def save_output(data: Any, filename: str):
with open(filename, "w") as f:
json.dump(data, f, indent=2)
print(f"Successfully wrote results to {filename}")
def go_search(args: argparse.Namespace):
limit = min(args.limit, 100) if args.limit else 25
data = make_request(
"/ontology/go/search",
params={"query": args.query, "limit": limit, "page": args.page},
)
save_output(data, args.output)
def go_terms(args: argparse.Namespace):
path = f"/ontology/go/terms/{args.ids}"
if args.relation:
path += f"/{args.relation}"
if args.target_ids:
path += f"/{args.target_ids}"
data = make_request(path)
save_output(data, args.output)
def go_slim(args: argparse.Namespace):
params = {"slimsToIds": args.slimsToIds}
if args.slimsFromIds:
params["slimsFromIds"] = args.slimsFromIds
if args.relations:
params["relations"] = args.relations
data = make_request("/ontology/go/slim", params=params)
save_output(data, args.output)
def eco_search(args: argparse.Namespace):
limit = min(args.limit, 100) if args.limit else 25
data = make_request(
"/ontology/eco/search",
params={"query": args.query, "limit": limit, "page": args.page},
)
save_output(data, args.output)
def eco_terms(args: argparse.Namespace):
path = f"/ontology/eco/terms/{args.ids}"
if args.relation:
path += f"/{args.relation}"
data = make_request(path)
save_output(data, args.output)
def annotation_search(args: argparse.Namespace):
"""Searches for annotations using the QuickGO API.
Args:
args: An argparse namespace containing the command-line arguments.
Expected arguments include geneProductId, geneProductSubset,
geneProductType, goId, taxonId, evidenceCode, goUsage, qualifier, limit,
page, aspect, and output.
"""
params = {}
if args.geneProductId:
params["geneProductId"] = args.geneProductId
if args.geneProductSubset:
params["geneProductSubset"] = args.geneProductSubset
if args.geneProductType:
params["geneProductType"] = args.geneProductType
if args.goId:
params["goId"] = args.goId
if args.taxonId:
params["taxonId"] = args.taxonId
if args.evidenceCode:
params["evidenceCode"] = args.evidenceCode
if args.goUsage:
params["goUsage"] = args.goUsage
if args.qualifier:
params["qualifier"] = args.qualifier
if args.aspect:
params["aspect"] = args.aspect
if args.limit:
params["limit"] = min(args.limit, 100)
if args.page:
params["page"] = args.page
data = make_request("/annotation/search", params=params)
save_output(data, args.output)
def geneproduct_search(args: argparse.Namespace):
"""Searches for gene products using the QuickGO API.
Args:
args: An argparse namespace containing the command-line arguments.
Expected arguments include query, taxonId, limit, page, and output.
"""
params = {}
if args.query:
params["query"] = args.query
if args.taxonId:
params["taxonId"] = args.taxonId
if args.limit:
params["limit"] = min(args.limit, 100)
if args.page:
params["page"] = args.page
data = make_request("/geneproduct/search", params=params)
save_output(data, args.output)
def main():
parser = argparse.ArgumentParser(description="QuickGO API CLI Wrapper")
subparsers = parser.add_subparsers(dest="command", required=True)
# GO Subcommands
go_parser = subparsers.add_parser("go", help="Gene Ontology endpoints")
go_sub = go_parser.add_subparsers(dest="go_cmd", required=True)
go_search_p = go_sub.add_parser("search", help="Search GO terms by text")
go_search_p.add_argument(
"--query", required=True, help="Text query (e.g. 'apoptosis')"
)
go_search_p.add_argument(
"--limit", type=int, default=25, help="Max results per page (max 100)"
)
go_search_p.add_argument("--page", type=int, default=1, help="Page number")
go_search_p.add_argument("--output", required=True, help="Output JSON file")
go_search_p.set_defaults(func=go_search)
go_terms_p = go_sub.add_parser("terms", help="Get GO term details")
go_terms_p.add_argument("--ids", required=True, help="Comma-separated GO IDs")
go_terms_p.add_argument(
"--relation",
choices=["ancestors", "descendants", "children", "complete", "paths"],
help="Relationship type",
)
go_terms_p.add_argument(
"--target_ids", help="Target IDs (only used with 'paths' relation)"
)
go_terms_p.add_argument("--output", required=True, help="Output JSON file")
go_terms_p.set_defaults(func=go_terms)
go_slim_p = go_sub.add_parser("slim", help="Calculate GO Slims")
go_slim_p.add_argument("--slimsToIds", required=True, help="Target slim IDs")
go_slim_p.add_argument("--slimsFromIds", help="Source IDs")
go_slim_p.add_argument("--relations", help="Comma-separated relations")
go_slim_p.add_argument("--output", required=True, help="Output JSON file")
go_slim_p.set_defaults(func=go_slim)
# ECO Subcommands
eco_parser = subparsers.add_parser(
"eco", help="Evidence & Conclusion Ontology"
)
eco_sub = eco_parser.add_subparsers(dest="eco_cmd", required=True)
eco_search_p = eco_sub.add_parser("search", help="Search ECO terms by text")
eco_search_p.add_argument("--query", required=True)
eco_search_p.add_argument(
"--limit", type=int, default=25, help="Max results per page (max 100)"
)
eco_search_p.add_argument("--page", type=int, default=1, help="Page number")
eco_search_p.add_argument("--output", required=True)
eco_search_p.set_defaults(func=eco_search)
eco_terms_p = eco_sub.add_parser("terms", help="Get ECO term details")
eco_terms_p.add_argument("--ids", required=True)
eco_terms_p.add_argument(
"--relation",
choices=["ancestors", "descendants", "children", "complete", "paths"],
)
eco_terms_p.add_argument("--output", required=True)
eco_terms_p.set_defaults(func=eco_terms)
# Annotation Subcommands
ann_parser = subparsers.add_parser(
"annotation", help="Annotation search endpoints"
)
ann_sub = ann_parser.add_subparsers(dest="ann_cmd", required=True)
ann_search_p = ann_sub.add_parser("search", help="Search annotations")
ann_search_p.add_argument(
"--geneProductId", help="Gene product ID (e.g. UniProtKB:P04637)"
)
ann_search_p.add_argument(
"--geneProductSubset", help="Gene product subset (e.g. Swiss-Prot)"
)
ann_search_p.add_argument(
"--geneProductType", help="Gene product type (e.g. protein)"
)
ann_search_p.add_argument("--goId", help="GO ID (e.g. GO:0006915)")
ann_search_p.add_argument(
"--taxonId", type=int, help="NCBI Taxon ID (e.g. 9606 for human)"
)
ann_search_p.add_argument(
"--evidenceCode", help="Evidence code (e.g. ECO:0000269 for EXP)"
)
ann_search_p.add_argument(
"--goUsage", choices=["exact", "slim", "desc"], help="How to use goId"
)
ann_search_p.add_argument(
"--qualifier", help="Qualifier (e.g. enables, part_of, involved_in)"
)
ann_search_p.add_argument(
"--aspect",
choices=[
"biological_process",
"molecular_function",
"cellular_component",
],
help="GO aspect",
)
ann_search_p.add_argument(
"--limit", type=int, default=25, help="Max results per page (max 100)"
)
ann_search_p.add_argument("--page", type=int, default=1, help="Page number")
ann_search_p.add_argument("--output", required=True, help="Output JSON file")
ann_search_p.set_defaults(func=annotation_search)
# Gene Product Subcommands
gp_parser = subparsers.add_parser(
"geneproduct", help="Gene Product search endpoints"
)
gp_sub = gp_parser.add_subparsers(dest="gp_cmd", required=True)
gp_search_p = gp_sub.add_parser("search", help="Search gene products")
gp_search_p.add_argument(
"--query", required=True, help="Query string (e.g. PROC)"
)
gp_search_p.add_argument(
"--taxonId", type=int, help="NCBI Taxon ID (e.g. 9606 for human)"
)
gp_search_p.add_argument(
"--limit", type=int, default=25, help="Max results per page (max 100)"
)
gp_search_p.add_argument("--page", type=int, default=1, help="Page number")
gp_search_p.add_argument("--output", required=True, help="Output JSON file")
gp_search_p.set_defaults(func=geneproduct_search)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
SKILL.md
---
name: quickgo-database
description: >
Query the QuickGO and Evidence & Conclusion Ontology (ECO) REST API. Use this
when you need to map genes to biological processes, molecular functions, or
cellular components, find genes associated with a specific pathway/GO term, or
explore the Gene Ontology hierarchy. Do not use for querying drug targets (use
OpenTargets) or mechanistic signaling pathway diagrams (use KEGG).
---
# QuickGO Database Skill
GO (Gene Ontology) annotations are one of the main ways to label a gene's
function. QuickGO is a fast, web-based browser for the GO and Evidence &
Conclusion Ontology (ECO), maintained by the Gene Ontology Annotation (GOA)
group at EMBL-EBI.
It provides a centralised resource to explore the functional attributes of gene
products (proteins, RNA, and complexes). It is a primary tool for functional
annotation mapping since it allows you to link a gene (e.g., USH2A) to its
specific biological processes (e.g. sensory perception of light stimulus),
molecular functions, and cellular components.
## Prerequisites
1. **`uv`**: Read the `uv` skill and follow its Setup instructions to ensure
`uv` is installed and on PATH.
2. **User Notification**: If .licenses/quickgo_database_LICENSE.txt does not
already exist in the workspace root directory then (1) prominently notify
the user to check the terms at https://www.ebi.ac.uk/QuickGO/ and
https://www.ebi.ac.uk/QuickGO/api/index.html, then (2) create the file
recording the notification text and timestamp.
## Usage
This skill provides a Python CLI wrapper `scripts/quickgo_tool.py` that queries
the QuickGO REST API. It handles formatting the requests, respecting rate
limits, and safely storing the potentially large JSON responses.
## Core Rules
- **Use the Wrapper**: ALWAYS execute the provided helper scripts to query the
database rather than accessing the database directly. The scripts
automatically enforce the required rate limit gracefully.
- **Pagination & Limits**: Restrict endpoints to a maximum of 100 results per
page using `--limit 100` and the `--page` parameter for larger datasets.
- **Output Files**: Always use the `--output` flag to save responses to a file
incrementally or parse via `jq`.
- **Evidence Codes**: Prioritize experimental evidence (`ECO:0000269`) over
electronic (`ECO:0000501`) to avoid noisy predictions.
- **Taxon Filtering**: Use `--taxonId 9606` to restrict results to Human when
analysing clinical or human genomic data.
- **Notification**: If this skill is used, ensure this is mentioned in the
output.
The tool has four main subcommands:
1. **`go`**: For retrieving information about GO terms (e.g. definitions,
ancestors, descendants, and slims). See
[references/go_terms.md](references/go_terms.md).
2. **`annotation`**: For finding functional annotations linking gene products
to GO terms. This is your primary functional mapper. See
[references/annotations.md](references/annotations.md).
3. **`geneproduct`**: For resolving gene symbols (like `PROC`) to their formal
database identifiers. See
[references/gene_products.md](references/gene_products.md).
4. **`eco`**: For Evidence & Conclusion Ontology terms (used in annotations to
indicate how an annotation was derived, e.g. experimental vs electronic).
See [references/eco_terms.md](references/eco_terms.md).
## Common Workflows
### 1. Map a gene to its functions (Annotations)
To find out what a gene does, you must first resolve its symbol to a UniProtKB
ID, and then query its annotations. Often it is best to filter for experimental
evidence (e.g. `ECO:0000269` for EXP, or others like IDA, IMP) to avoid noisy
electronic predictions.
```bash
# Step 1: Find the UniProtKB ID for human (9606) gene PROC
uv run scripts/quickgo_tool.py geneproduct search --query "PROC" --taxonId 9606 --limit 5 --output proc_id.json
# (Look at proc_id.json, observe the ID is e.g., UniProtKB:P04070)
# Step 2: Find experimental GO annotations for that ID
uv run scripts/quickgo_tool.py annotation search --geneProductId "UniProtKB:P04070" --taxonId 9606 --evidenceCode "ECO:0000269" --limit 50 --output proc_annotations.json
```
### 2. Find all genes in a pathway
To find all genes annotated to a specific GO term (e.g., GO:0003700 for
"transcription factor activity"):
```bash
# Find human genes with this specific molecular function
uv run scripts/quickgo_tool.py annotation search --goId "GO:0003700" --taxonId 9606 --limit 50 --output tf_genes.json
```
### 3. Explore the GO Hierarchy
To check if a specific GO term is a descendant of a broader category, or to
fetch its definition:
```bash
# Fetch term details (definitions, synonyms)
uv run scripts/quickgo_tool.py go terms --ids "GO:0003150" --output term_details.json
# Check ancestry (e.g., is GO:0001917 a child of something?)
uv run scripts/quickgo_tool.py go terms --ids "GO:0001917" --relation ancestors --output term_ancestors.json
```
### 4. Create a GO Slim Summary
If you have a list of candidate genes and want a high-level functional summary,
you can map them up to a predefined GO Slim. First, fetch the annotations for
the genes to extract their GO IDs, then pass those IDs to the slim endpoint:
```bash
# Step 1: Find GO IDs for candidate genes (e.g., via their UniProt IDs, fetching their annotations)
# ... (output yields e.g., GO:0006915,GO:0008219)
# Step 2: Create a slim summary from those specific GO IDs
uv run scripts/quickgo_tool.py go slim --slimsToIds "GO:0005575,GO:0008150,GO:0003674" --slimsFromIds "GO:0006915,GO:0008219" --output my_slim.json
```