Skip to content

Commit

Permalink
Merge pull request #107 from geneontology/exception_handler
Browse files Browse the repository at this point in the history
no data found exception handler
  • Loading branch information
sierra-moxon authored Nov 25, 2024
2 parents bb6d386 + 8f91cc0 commit 7952def
Show file tree
Hide file tree
Showing 28 changed files with 1,312 additions and 660 deletions.
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ integration-tests:
poetry run pytest tests/integration/step_defs/*.py

lint:
poetry run tox -e flake8
poetry run tox -e lint-fix

spell:
poetry run tox -e codespell

unit-tests:
poetry run pytest tests/unit/*.py
poetry run pytest -v tests/unit/*.py

export-requirements:
poetry export -f requirements.txt --output requirements.txt
Expand All @@ -38,12 +39,14 @@ install:
poetry install

help:
@echo ""
@echo "##################################################################################################"
@echo "make all -- installs requirements, deploys and starts the site locally"
@echo "make install -- install dependencies"
@echo "make start -- start the API locally"
@echo "make test -- runs tests, linter in fix mode, and spell checker"
@echo "make lint -- runs linter in fix mode"
@echo "make spell -- runs spell checker"
@echo "make help -- show this help"
@echo ""
@echo "make start -- start the API locally at localhost:8080/docs (takes about 10 seconds to start)"
@echo "make start-dev -- start the API locally at localhost:8081/docs (takes about 10 seconds to start)"
@echo "##################################################################################################"
45 changes: 45 additions & 0 deletions app/exceptions/global_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Global exception handlers for API endpoints."""

from fastapi import HTTPException


class DataNotFoundException(HTTPException):
"""
Exception for when data is not found.
:param detail: The detail message for the exception.
:type detail: str, optional
:returns: A DataNotFoundException object.
"""

def __init__(self, detail: str = "Data not found"):
"""
Initialize the DataNotFoundException object.
:param detail:
:type detail:
:returns:
"""
super().__init__(status_code=404, detail=detail)


class InvalidIdentifier(HTTPException):
"""
Exception for when data is not found.
:param detail: The detail message for the exception.
:type detail: str, optional
:returns: A DataNotFoundException object.
"""

def __init__(self, detail: str = "Data not found"):
"""
Initialize the DataNotFoundException object.
:param detail:
:type detail:
:returns:
"""
super().__init__(status_code=400, detail=detail)
16 changes: 14 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from app.exceptions.global_exceptions import DataNotFoundException
from app.middleware.logging_middleware import LoggingMiddleware
from app.routers import (
bioentity,
Expand All @@ -15,7 +16,6 @@
ontology,
pathway_widget,
prefixes,
publications,
ribbon,
search,
slimmer,
Expand All @@ -24,6 +24,7 @@

logger = logging.getLogger("uvicorn.error")


app = FastAPI(
title="GO API",
description="The Gene Ontology API.\n\n __Source:__ 'https://github.com/geneontology/go-fastapi'",
Expand All @@ -44,7 +45,6 @@
app.include_router(prefixes.router)
app.include_router(labeler.router)
app.include_router(search.router)
app.include_router(publications.router)
app.include_router(users_and_groups.router)

# Logging
Expand Down Expand Up @@ -82,5 +82,17 @@ async def general_exception_handler(request: Request, exc: Exception):
)


@app.exception_handler(DataNotFoundException)
async def data_not_found_exception_handler(request: Request, exc: DataNotFoundException):
"""
Global exception handler for DataNotFoundException.
:param request:
:param exc:
:return:
"""
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})


if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8080, log_level="info", reload=True)
56 changes: 48 additions & 8 deletions app/routers/bioentity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
from ontobio.config import get_config
from ontobio.golr.golr_associations import search_associations

from app.utils.golr_utils import gu_run_solr_text_on
from app.exceptions.global_exceptions import DataNotFoundException, InvalidIdentifier
from app.utils.golr_utils import gu_run_solr_text_on, is_valid_bioentity
from app.utils.settings import ESOLR, ESOLRDoc, get_user_agent

from ..utils.ontology_utils import is_valid_goid
from .slimmer import gene_to_uniprot_from_mygene

INVOLVED_IN = "involved_in"
Expand Down Expand Up @@ -78,6 +80,13 @@ async def get_bioentity_by_id(
'start' determines the starting index for fetching results, and 'rows' specifies
the number of results to be retrieved per page.
"""
try:
is_valid_bioentity(id)
except DataNotFoundException as e:
raise DataNotFoundException(detail=str(e)) from e
except ValueError as e:
raise InvalidIdentifier(detail=str(e)) from e

if rows is None:
rows = 100000
# special case MGI, sigh
Expand All @@ -91,11 +100,12 @@ async def get_bioentity_by_id(
# query_filters is translated to the qf solr parameter
# boost fields %5E2 -> ^2, %5E1 -> ^1
query_filters = "bioentity%5E2"
logger.info(id)

optionals = "&defType=edismax&start=" + str(start) + "&rows=" + str(rows)
# id here is passed to solr q parameter, query_filters go to the boost, fields are what's returned
bioentity = gu_run_solr_text_on(ESOLR.GOLR, ESOLRDoc.BIOENTITY, id, query_filters, fields, optionals, False)
if not bioentity:
raise DataNotFoundException(detail=f"Item with ID {id} not found")
return bioentity


Expand Down Expand Up @@ -141,6 +151,13 @@ async def get_annotations_by_goterm_id(
'start' determines the starting index for fetching results, and 'rows' specifies
the number of results to be retrieved per page.
"""
try:
is_valid_goid(id)
except DataNotFoundException as e:
raise DataNotFoundException(detail=str(e)) from e
except ValueError as e:
raise InvalidIdentifier(detail=str(e)) from e

if rows is None:
rows = 100000
# dictates the fields to return, annotation_class,aspect
Expand Down Expand Up @@ -170,7 +187,8 @@ async def get_annotations_by_goterm_id(

optionals = "&defType=edismax&start=" + str(start) + "&rows=" + str(rows) + evidence
data = gu_run_solr_text_on(ESOLR.GOLR, ESOLRDoc.ANNOTATION, id, query_filters, fields, optionals, False)

if not data:
raise DataNotFoundException(detail=f"Item with ID {id} not found")
return data


Expand Down Expand Up @@ -224,9 +242,16 @@ async def get_genes_by_goterm_id(
and 'annotation_extension_class_label' associated with the provided GO term.
"""
try:
is_valid_goid(id)
except DataNotFoundException as e:
raise DataNotFoundException(detail=str(e)) from e
except ValueError as e:
raise InvalidIdentifier(detail=str(e)) from e

if rows is None:
rows = 100000
association_return = {}

if relationship_type == ACTS_UPSTREAM_OF_OR_WITHIN:
association_return = search_associations(
subject_category="gene",
Expand Down Expand Up @@ -273,6 +298,7 @@ async def get_genes_by_goterm_id(
url=ESOLR.GOLR,
rows=rows,
)

return {"associations": association_return.get("associations")}


Expand Down Expand Up @@ -310,6 +336,13 @@ async def get_taxon_by_goterm_id(
:return: A dictionary containing the taxon information for genes annotated to the provided GO term.
The dictionary will contain fields such as 'taxon' and 'taxon_label' associated with the genes.
"""
try:
is_valid_goid(id)
except DataNotFoundException as e:
raise DataNotFoundException(detail=str(e)) from e
except ValueError as e:
raise InvalidIdentifier(detail=str(e)) from e

if rows is None:
rows = 100000
fields = "taxon,taxon_label"
Expand Down Expand Up @@ -342,7 +375,8 @@ async def get_taxon_by_goterm_id(

optionals = "&defType=edismax&start=" + str(start) + "&rows=" + str(rows) + evidence + taxon_restrictions
data = gu_run_solr_text_on(ESOLR.GOLR, ESOLRDoc.ANNOTATION, id, query_filters, fields, optionals, False)

if not data:
raise DataNotFoundException(detail=f"Item with ID {id} not found")
return data


Expand Down Expand Up @@ -394,6 +428,13 @@ async def get_annotations_by_gene_id(
scenes for querying.
"""
try:
is_valid_bioentity(id)
except DataNotFoundException as e:
raise DataNotFoundException(detail=str(e)) from e
except ValueError as e:
raise InvalidIdentifier(detail=str(e)) from e

if rows is None:
rows = 100000

Expand All @@ -410,8 +451,6 @@ async def get_annotations_by_gene_id(
rows=rows,
slim=slim,
)
logger.info("should be null assocs")
logger.info(assocs)
# If there are no associations for the given ID, try other IDs.
# Note the AmiGO instance does *not* support equivalent IDs
if len(assocs["associations"]) == 0:
Expand All @@ -435,6 +474,7 @@ async def get_annotations_by_gene_id(
num_found = num_found + pr_assocs.get("numFound")
assocs["numFound"] = num_found
for asc in pr_assocs["associations"]:
logger.info(asc)
assocs["associations"].append(asc)
if not assocs or assocs["associations"] == 0:
raise DataNotFoundException(detail=f"Item with ID {id} not found")
return {"associations": assocs.get("associations")}
6 changes: 5 additions & 1 deletion app/routers/labeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from fastapi import APIRouter, Query

from app.exceptions.global_exceptions import DataNotFoundException
from app.utils.ontology_utils import batch_fetch_labels
from app.utils.settings import get_user_agent

Expand All @@ -22,4 +23,7 @@ async def expand_curie(
):
"""Fetches a map from IDs to labels e.g. GO:0003677."""
logger.info("fetching labels for IDs")
return batch_fetch_labels(id)
labels = batch_fetch_labels(id)
if not labels:
raise DataNotFoundException(detail=f"Item with ID {id} not found")
return labels
Loading

0 comments on commit 7952def

Please sign in to comment.