Skip to content

Commit 511dfa7

Browse files
authored
Merge pull request #20 from VariantEffect/error-reporting
Error reporting
2 parents 8c734b5 + cc8f3ad commit 511dfa7

File tree

19 files changed

+698
-232
lines changed

19 files changed

+698
-232
lines changed

.github/workflows/checks.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ jobs:
44
test:
55
name: test py${{ matrix.python-version }}
66
runs-on: ubuntu-latest
7+
env:
8+
MAVEDB_BASE_URL: https://api.mavedb.org
79
strategy:
810
matrix:
911
python-version: ["3.11", "3.12"]

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ RUN pip install -e '.[dev,tests]'
4646
RUN pip install -U polars-lts-cpu
4747
# install gene normalizer with pg dependencies. TODO: can the pg dependencies be specified in pyproject.toml?
4848
#RUN pip install 'gene-normalizer[pg]'
49+
50+
# not working, needs to happen after db volume is mounted
51+
# ENV GENE_NORM_DB_URL=postgres://postgres:postgres@db:5432/gene_normalizer
52+
# RUN echo "y" | gene_norm_update_remote
53+
4954
ENV PYTHONUNBUFFERED 1
5055

5156
ENV PYTHONPATH "${PYTHONPATH}:/usr/src/app/src"
57+
58+
# Tell Docker that we will listen on port 8000.
59+
EXPOSE 8000
60+
61+
# At container startup, run the application using uvicorn.
62+
CMD ["uvicorn", "api.server_main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose-dev.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ services:
3434
volumes:
3535
- vrs-mapping-seqrepo-dev:/usr/local/share/seqrepo
3636

37+
api:
38+
build:
39+
context: .
40+
command: bash -c "uvicorn api.server_main:app --host 0.0.0.0 --port 8000 --reload"
41+
depends_on:
42+
- db
43+
- seqrepo
44+
env_file:
45+
- settings/.env.dev
46+
ports:
47+
- "8004:8000"
48+
volumes:
49+
- .:/usr/src/app
50+
- vrs-mapping-seqrepo-dev:/usr/local/share/seqrepo
51+
3752
volumes:
3853
vrs-mapping-data-dev:
3954
vrs-mapping-seqrepo-dev:

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ dependencies = [
4242
"pydantic>=2",
4343
"python-dotenv",
4444
"setuptools>=68.0", # tmp -- ensure 3.12 compatibility
45-
"mavehgvs==0.6.1"
45+
"mavehgvs==0.6.1",
46+
"fastapi",
47+
"starlette",
48+
"uvicorn"
4649
]
4750
dynamic = ["version"]
4851

settings/.env.dev

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ POSTGRES_DB=gene_normalizer
1919

2020
UTA_DB_URL=postgresql://anonymous:[email protected]:5432/uta/uta_20180821
2121

22+
####################################################################################################
23+
# Environment variables for MaveDB connection
24+
####################################################################################################
25+
26+
MAVEDB_BASE_URL=http://localhost:8000
27+
MAVEDB_API_KEY=
28+
2229
####################################################################################################
2330
# Environment variables for seqrepo
2431
####################################################################################################

src/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Provide VRS mapping utilities API"""

src/api/routers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Provide routers for dcd mapping API"""

src/api/routers/map.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
""""Provide mapping router"""
2+
from cool_seq_tool.schemas import AnnotationLayer
3+
from fastapi import APIRouter, HTTPException
4+
from fastapi.responses import JSONResponse
5+
from requests import HTTPError
6+
7+
from dcd_mapping.align import AlignmentError, BlatNotFoundError, align
8+
from dcd_mapping.annotate import (
9+
_get_computed_reference_sequence,
10+
_get_mapped_reference_sequence,
11+
_set_scoreset_layer,
12+
annotate,
13+
)
14+
from dcd_mapping.lookup import DataLookupError
15+
from dcd_mapping.mavedb_data import (
16+
ScoresetNotSupportedError,
17+
get_raw_scoreset_metadata,
18+
get_scoreset_metadata,
19+
get_scoreset_records,
20+
)
21+
from dcd_mapping.resource_utils import ResourceAcquisitionError
22+
from dcd_mapping.schemas import ScoreAnnotation, ScoresetMapping, VrsVersion
23+
from dcd_mapping.transcripts import TxSelectError, select_transcript
24+
from dcd_mapping.vrs_map import VrsMapError, vrs_map
25+
26+
router = APIRouter(
27+
prefix="/api/v1", tags=["mappings"], responses={404: {"description": "Not found"}}
28+
)
29+
30+
31+
@router.post(path="/map/{urn}", status_code=200, response_model=ScoresetMapping)
32+
async def map_scoreset(urn: str) -> ScoresetMapping:
33+
"""Perform end-to-end mapping for a scoreset.
34+
35+
:param urn: identifier for a scoreset.
36+
:param output_path: optional path to save output at
37+
:param vrs_version: version of VRS objects to output (1.3 or 2)
38+
:param silent: if True, suppress console information output
39+
"""
40+
try:
41+
metadata = get_scoreset_metadata(urn)
42+
records = get_scoreset_records(urn, True)
43+
except ScoresetNotSupportedError as e:
44+
return ScoresetMapping(
45+
metadata=None,
46+
error_message=str(e).strip("'"),
47+
)
48+
except ResourceAcquisitionError as e:
49+
msg = f"Unable to acquire resource from MaveDB: {e}"
50+
raise HTTPException(status_code=500, detail=msg) from e
51+
52+
try:
53+
alignment_result = align(metadata, True)
54+
except BlatNotFoundError as e:
55+
msg = "BLAT command appears missing. Ensure it is available on the $PATH or use the environment variable BLAT_BIN_PATH to point to it. See instructions in the README prerequisites section for more."
56+
raise HTTPException(status_code=500, detail=msg) from e
57+
except ResourceAcquisitionError as e:
58+
msg = f"BLAT resource could not be acquired: {e}"
59+
raise HTTPException(status_code=500, detail=msg) from e
60+
except AlignmentError as e:
61+
return JSONResponse(
62+
content=ScoresetMapping(
63+
metadata=metadata, error_message=str(e).strip("'")
64+
).model_dump(exclude_none=True)
65+
)
66+
67+
try:
68+
transcript = await select_transcript(metadata, records, alignment_result)
69+
except (TxSelectError, KeyError, ValueError) as e:
70+
return JSONResponse(
71+
content=ScoresetMapping(
72+
metadata=metadata, error_message=str(e).strip("'")
73+
).model_dump(exclude_none=True)
74+
)
75+
except HTTPError as e:
76+
msg = f"HTTP error occurred during transcript selection: {e}"
77+
raise HTTPException(status_code=500, detail=msg) from e
78+
except DataLookupError as e:
79+
msg = f"Data lookup error occurred during transcript selection: {e}"
80+
raise HTTPException(status_code=500, detail=msg) from e
81+
82+
try:
83+
vrs_results = vrs_map(metadata, alignment_result, records, transcript, True)
84+
except VrsMapError as e:
85+
return JSONResponse(
86+
content=ScoresetMapping(
87+
metadata=metadata, error_message=str(e).strip("'")
88+
).model_dump(exclude_none=True)
89+
)
90+
if vrs_results is None:
91+
return ScoresetMapping(
92+
metadata=metadata,
93+
error_message="No variant mappings available for this score set",
94+
)
95+
96+
try:
97+
vrs_results = annotate(vrs_results, transcript, metadata, VrsVersion.V_2)
98+
except Exception as e:
99+
return JSONResponse(
100+
content=ScoresetMapping(
101+
metadata=metadata, error_message=str(e).strip("'")
102+
).model_dump(exclude_none=True)
103+
)
104+
if vrs_results is None:
105+
return ScoresetMapping(
106+
metadata=metadata,
107+
error_message="No annotated variant mappings available for this score set",
108+
)
109+
110+
try:
111+
raw_metadata = get_raw_scoreset_metadata(urn)
112+
preferred_layers = {
113+
_set_scoreset_layer(urn, vrs_results),
114+
}
115+
116+
reference_sequences = {
117+
layer: {
118+
"computed_reference_sequence": None,
119+
"mapped_reference_sequence": None,
120+
}
121+
for layer in AnnotationLayer
122+
}
123+
124+
for layer in preferred_layers:
125+
reference_sequences[layer][
126+
"computed_reference_sequence"
127+
] = _get_computed_reference_sequence(urn, layer, transcript)
128+
reference_sequences[layer][
129+
"mapped_reference_sequence"
130+
] = _get_mapped_reference_sequence(layer, transcript, alignment_result)
131+
132+
mapped_scores: list[ScoreAnnotation] = []
133+
for m in vrs_results:
134+
if m.annotation_layer in preferred_layers:
135+
# drop annotation layer from mapping object
136+
mapped_scores.append(ScoreAnnotation(**m.model_dump()))
137+
except Exception as e:
138+
return JSONResponse(
139+
content=ScoresetMapping(
140+
metadata=metadata, error_message=str(e).strip("'")
141+
).model_dump(exclude_none=True)
142+
)
143+
144+
return JSONResponse(
145+
content=ScoresetMapping(
146+
metadata=raw_metadata,
147+
computed_protein_reference_sequence=reference_sequences[
148+
AnnotationLayer.PROTEIN
149+
]["computed_reference_sequence"],
150+
mapped_protein_reference_sequence=reference_sequences[
151+
AnnotationLayer.PROTEIN
152+
]["mapped_reference_sequence"],
153+
computed_genomic_reference_sequence=reference_sequences[
154+
AnnotationLayer.GENOMIC
155+
]["computed_reference_sequence"],
156+
mapped_genomic_reference_sequence=reference_sequences[
157+
AnnotationLayer.GENOMIC
158+
]["mapped_reference_sequence"],
159+
mapped_scores=mapped_scores,
160+
).model_dump(exclude_none=True)
161+
)

src/api/server_main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""FastAPI server file"""
2+
import uvicorn
3+
from fastapi import FastAPI
4+
5+
from api.routers import map
6+
7+
app = FastAPI()
8+
9+
app.include_router(map.router)
10+
11+
12+
# If the application is not already being run within a uvicorn server, start uvicorn here.
13+
if __name__ == "__main__":
14+
uvicorn.run(app, host="0.0.0.0", port=8000) # noqa: S104

src/dcd_mapping/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
from dotenv import load_dotenv
99

1010
from .main import map_scoreset, map_scoreset_urn
11+
from .version import dcd_mapping_version
1112

1213
__all__ = ["map_scoreset", "map_scoreset_urn"]
14+
__version__ = dcd_mapping_version
1315

1416
load_dotenv()

0 commit comments

Comments
 (0)