diff --git a/server/curfu/main.py b/server/curfu/main.py index 80187b0b..d4df661e 100644 --- a/server/curfu/main.py +++ b/server/curfu/main.py @@ -22,6 +22,17 @@ from curfu.version import __version__ as curfu_version fastapi_app = FastAPI( + title="Fusion Curation API", + description="Provide data functions to support [VICC Fusion Curation interface](fusion-builder.cancervariants.org/).", + contact={ + "name": "Alex H. Wagner", + "email": "Alex.Wagner@nationwidechildrens.org", + "url": "https://www.nationwidechildrens.org/specialties/institute-for-genomic-medicine/research-labs/wagner-lab", + }, + license={ + "name": "MIT", + "url": "https://github.com/cancervariants/fusion-curation/blob/main/LICENSE", + }, version=curfu_version, swagger_ui_parameters={"tryItOutEnabled": True}, docs_url="/docs", @@ -63,7 +74,7 @@ def serve_react_app(app: FastAPI) -> FastAPI: ) templates = Jinja2Templates(directory=BUILD_DIR.as_posix()) - @app.get("/{full_path:path}") + @app.get("/{full_path:path}", include_in_schema=False) async def serve_react_app(request: Request, full_path: str) -> TemplateResponse: """Add arbitrary path support to FastAPI service. diff --git a/server/curfu/routers/complete.py b/server/curfu/routers/complete.py index 7faa75a5..5b03bb71 100644 --- a/server/curfu/routers/complete.py +++ b/server/curfu/routers/complete.py @@ -4,7 +4,12 @@ from fastapi import APIRouter, Query, Request from curfu import MAX_SUGGESTIONS, LookupServiceError -from curfu.schemas import AssociatedDomainResponse, ResponseDict, SuggestGeneResponse +from curfu.schemas import ( + AssociatedDomainResponse, + ResponseDict, + RouteTag, + SuggestGeneResponse, +) router = APIRouter() @@ -14,6 +19,7 @@ operation_id="suggestGene", response_model=SuggestGeneResponse, response_model_exclude_none=True, + tags=[RouteTag.COMPLETION], ) def suggest_gene(request: Request, term: str = Query("")) -> ResponseDict: """Provide completion suggestions for term provided by user. @@ -52,6 +58,7 @@ def suggest_gene(request: Request, term: str = Query("")) -> ResponseDict: operation_id="suggestDomain", response_model=AssociatedDomainResponse, response_model_exclude_none=True, + tags=[RouteTag.COMPLETION], ) def suggest_domain(request: Request, gene_id: str = Query("")) -> ResponseDict: """Provide possible domains associated with a given gene to be selected by a user. diff --git a/server/curfu/routers/constructors.py b/server/curfu/routers/constructors.py index 88120656..fab4542e 100644 --- a/server/curfu/routers/constructors.py +++ b/server/curfu/routers/constructors.py @@ -12,6 +12,7 @@ GetDomainResponse, RegulatoryElementResponse, ResponseDict, + RouteTag, TemplatedSequenceElementResponse, TxSegmentElementResponse, ) @@ -25,6 +26,7 @@ operation_id="buildGeneElement", response_model=GeneElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) def build_gene_element(request: Request, term: str = Query("")) -> GeneElementResponse: """Construct valid gene element given user-provided term. @@ -48,6 +50,7 @@ def build_gene_element(request: Request, term: str = Query("")) -> GeneElementRe operation_id="buildTranscriptSegmentElementECT", response_model=TxSegmentElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) async def build_tx_segment_ect( request: Request, @@ -85,6 +88,7 @@ async def build_tx_segment_ect( operation_id="buildTranscriptSegmentElementGCT", response_model=TxSegmentElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) async def build_tx_segment_gct( request: Request, @@ -132,6 +136,7 @@ async def build_tx_segment_gct( operation_id="buildTranscriptSegmentElementGCG", response_model=TxSegmentElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) async def build_tx_segment_gcg( request: Request, @@ -179,6 +184,7 @@ async def build_tx_segment_gcg( operation_id="buildTemplatedSequenceElement", response_model=TemplatedSequenceElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) def build_templated_sequence_element( request: Request, start: int, end: int, sequence_id: str, strand: str @@ -215,6 +221,7 @@ def build_templated_sequence_element( operation_id="getDomain", response_model=GetDomainResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) def build_domain( request: Request, @@ -260,6 +267,7 @@ def build_domain( operation_id="getRegulatoryElement", response_model=RegulatoryElementResponse, response_model_exclude_none=True, + tags=[RouteTag.CONSTRUCTORS], ) def build_regulatory_element( request: Request, element_class: RegulatoryClass, gene_name: str diff --git a/server/curfu/routers/demo.py b/server/curfu/routers/demo.py index 6feabb8b..434f3e13 100644 --- a/server/curfu/routers/demo.py +++ b/server/curfu/routers/demo.py @@ -31,6 +31,7 @@ GeneElement, LinkerElement, MultiplePossibleGenesElement, + RouteTag, TemplatedSequenceElement, TranscriptSegmentElement, UnknownGeneElement, @@ -156,6 +157,7 @@ def clientify_fusion(fusion: Fusion, fusor_instance: FUSOR) -> ClientFusion: operation_id="alkDemo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_alk(request: Request) -> DemoResponse: """Retrieve ALK assayed fusion. @@ -176,6 +178,7 @@ def get_alk(request: Request) -> DemoResponse: operation_id="ewsr1Demo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_ewsr1(request: Request) -> DemoResponse: """Retrieve EWSR1 assayed fusion. @@ -196,6 +199,7 @@ def get_ewsr1(request: Request) -> DemoResponse: operation_id="bcrAbl1Demo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_bcr_abl1(request: Request) -> DemoResponse: """Retrieve BCR-ABL1 categorical fusion. @@ -216,6 +220,7 @@ def get_bcr_abl1(request: Request) -> DemoResponse: operation_id="tpm3Ntrk1Demo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_tpm3_ntrk1(request: Request) -> DemoResponse: """Retrieve TPM3-NTRK1 assayed fusion. @@ -236,6 +241,7 @@ def get_tpm3_ntrk1(request: Request) -> DemoResponse: operation_id="tpm3PdgfrbDemo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_tpm3_pdgfrb(request: Request) -> DemoResponse: """Retrieve TPM3-PDGFRB assayed fusion. @@ -256,6 +262,7 @@ def get_tpm3_pdgfrb(request: Request) -> DemoResponse: operation_id="ighMycDemo", response_model=DemoResponse, response_model_exclude_none=True, + tags=[RouteTag.DEMOS], ) def get_igh_myc(request: Request) -> DemoResponse: """Retrieve IGH-MYC assayed fusion. diff --git a/server/curfu/routers/lookup.py b/server/curfu/routers/lookup.py index 45854b5a..4e66948f 100644 --- a/server/curfu/routers/lookup.py +++ b/server/curfu/routers/lookup.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Query, Request from curfu import LookupServiceError -from curfu.schemas import NormalizeGeneResponse, ResponseDict +from curfu.schemas import NormalizeGeneResponse, ResponseDict, RouteTag router = APIRouter() @@ -12,6 +12,7 @@ operation_id="normalizeGene", response_model=NormalizeGeneResponse, response_model_exclude_none=True, + tags=[RouteTag.LOOKUP], ) def normalize_gene(request: Request, term: str = Query("")) -> ResponseDict: """Normalize gene term provided by user. diff --git a/server/curfu/routers/meta.py b/server/curfu/routers/meta.py index 7313440f..2032de94 100644 --- a/server/curfu/routers/meta.py +++ b/server/curfu/routers/meta.py @@ -3,14 +3,17 @@ from fastapi import APIRouter from fusor import __version__ as fusor_version -from curfu.schemas import ServiceInfoResponse +from curfu.schemas import RouteTag, ServiceInfoResponse from curfu.version import __version__ as curfu_version router = APIRouter() @router.get( - "/api/service_info", operation_id="serviceInfo", response_model=ServiceInfoResponse + "/api/service_info", + operation_id="serviceInfo", + response_model=ServiceInfoResponse, + tags=[RouteTag.META], ) def get_service_info() -> ServiceInfoResponse: """Return service info.""" diff --git a/server/curfu/routers/nomenclature.py b/server/curfu/routers/nomenclature.py index 291954b9..710cdc84 100644 --- a/server/curfu/routers/nomenclature.py +++ b/server/curfu/routers/nomenclature.py @@ -18,7 +18,7 @@ from pydantic import ValidationError from curfu import logger -from curfu.schemas import NomenclatureResponse, ResponseDict +from curfu.schemas import NomenclatureResponse, ResponseDict, RouteTag router = APIRouter() @@ -28,6 +28,7 @@ operation_id="regulatoryElementNomenclature", response_model=NomenclatureResponse, response_model_exclude_none=True, + tags=[RouteTag.NOMENCLATURE], ) def generate_regulatory_element_nomenclature( request: Request, regulatory_element: Dict = Body() @@ -69,6 +70,7 @@ def generate_regulatory_element_nomenclature( operation_id="txSegmentNomenclature", response_model=NomenclatureResponse, response_model_exclude_none=True, + tags=[RouteTag.NOMENCLATURE], ) def generate_tx_segment_nomenclature(tx_segment: Dict = Body()) -> ResponseDict: """Build transcript segment element nomenclature. @@ -96,6 +98,7 @@ def generate_tx_segment_nomenclature(tx_segment: Dict = Body()) -> ResponseDict: operation_id="templatedSequenceNomenclature", response_model=NomenclatureResponse, response_model_exclude_none=True, + tags=[RouteTag.NOMENCLATURE], ) def generate_templated_seq_nomenclature( request: Request, templated_sequence: Dict = Body() @@ -136,6 +139,7 @@ def generate_templated_seq_nomenclature( operation_id="geneNomenclature", response_model=NomenclatureResponse, response_model_exclude_none=True, + tags=[RouteTag.NOMENCLATURE], ) def generate_gene_nomenclature(gene_element: Dict = Body()) -> ResponseDict: """Build gene element nomenclature. @@ -170,6 +174,7 @@ def generate_gene_nomenclature(gene_element: Dict = Body()) -> ResponseDict: operation_id="fusionNomenclature", response_model=NomenclatureResponse, response_model_exclude_none=True, + tags=[RouteTag.NOMENCLATURE], ) def generate_fusion_nomenclature( request: Request, fusion: Dict = Body() diff --git a/server/curfu/routers/utilities.py b/server/curfu/routers/utilities.py index 4ffed21a..2ce7799c 100644 --- a/server/curfu/routers/utilities.py +++ b/server/curfu/routers/utilities.py @@ -13,6 +13,7 @@ from curfu.schemas import ( CoordsUtilsResponse, GetTranscriptsResponse, + RouteTag, SequenceIDResponse, ) from curfu.sequence_services import InvalidInputError, get_strand @@ -25,6 +26,7 @@ operation_id="getMANETranscripts", response_model=GetTranscriptsResponse, response_model_exclude_none=True, + tags=[RouteTag.UTILITIES], ) def get_mane_transcripts(request: Request, term: str) -> Dict: """Get MANE transcripts for gene term. @@ -54,6 +56,7 @@ def get_mane_transcripts(request: Request, term: str) -> Dict: operation_id="getGenomicCoords", response_model=CoordsUtilsResponse, response_model_exclude_none=True, + tags=[RouteTag.UTILITIES], ) async def get_genome_coords( request: Request, @@ -126,6 +129,7 @@ async def get_genome_coords( operation_id="getExonCoords", response_model=CoordsUtilsResponse, response_model_exclude_none=True, + tags=[RouteTag.UTILITIES], ) async def get_exon_coords( request: Request, @@ -185,6 +189,7 @@ async def get_exon_coords( operation_id="getSequenceId", response_model=SequenceIDResponse, response_model_exclude_none=True, + tags=[RouteTag.UTILITIES], ) async def get_sequence_id(request: Request, sequence: str) -> SequenceIDResponse: """Get GA4GH sequence ID and aliases given sequence sequence ID @@ -234,6 +239,7 @@ async def get_sequence_id(request: Request, sequence: str) -> SequenceIDResponse description="Given a known accession identifier, retrieve sequence data and return" "as a FASTA file", response_class=FileResponse, + tags=[RouteTag.UTILITIES], ) async def get_sequence( request: Request, diff --git a/server/curfu/routers/validate.py b/server/curfu/routers/validate.py index 5150c315..a3fc0371 100644 --- a/server/curfu/routers/validate.py +++ b/server/curfu/routers/validate.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Request from fusor.exceptions import FUSORParametersException -from curfu.schemas import ResponseDict, ValidateFusionResponse +from curfu.schemas import ResponseDict, RouteTag, ValidateFusionResponse router = APIRouter() @@ -14,6 +14,7 @@ operation_id="validateFusion", response_model=ValidateFusionResponse, response_model_exclude_none=True, + tags=[RouteTag.VALIDATORS], ) def validate_fusion(request: Request, fusion: Dict = Body()) -> ResponseDict: """Validate proposed Fusion object. Return warnings if invalid. diff --git a/server/curfu/schemas.py b/server/curfu/schemas.py index 41ca4bd4..9cd71c61 100644 --- a/server/curfu/schemas.py +++ b/server/curfu/schemas.py @@ -1,4 +1,5 @@ """Provide schemas for FastAPI responses.""" +from enum import Enum from typing import Dict, List, Literal, Optional, Tuple, Union from cool_seq_tool.schemas import GenomicData @@ -317,3 +318,17 @@ class DemoResponse(Response): """Response model for demo fusion object retrieval endpoints.""" fusion: Union[ClientAssayedFusion, ClientCategoricalFusion] + + +class RouteTag(str, Enum): + """Define tags for API routes.""" + + UTILITIES = "Utilities" + CONSTRUCTORS = "Constructors" + VALIDATORS = "Validators" + COMPLETION = "Completion" + NOMENCLATURE = "Nomenclature" + DEMOS = "Demos" + META = "Meta" + SERVICE = "Service" + LOOKUP = "Lookup"