Skip to content

Commit d56459c

Browse files
authored
Merge pull request #74 from 3D-Beacons/update
Added health and sequence summary endpoints
2 parents bc184f8 + b5624eb commit d56459c

11 files changed

Lines changed: 756 additions & 31 deletions

File tree

app/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from app import REDIS_URL
1313
from app.annotations.annotations import annotations_route
1414
from app.ensembl.ensembl import ensembl_route
15+
from app.health.health import health_route
1516
from app.sequence.sequence import sequence_route
1617
from app.uniprot.uniprot import uniprot_route
1718
from app.version import __version__ as schema_version
@@ -46,6 +47,7 @@
4647
app.include_router(ensembl_route, prefix="/ensembl")
4748
app.include_router(sequence_route, prefix="/sequence")
4849
app.include_router(annotations_route, prefix="/annotations")
50+
app.include_router(health_route, prefix="/health")
4951

5052
origins = ["*"]
5153

app/health/__init__.py

Whitespace-only changes.

app/health/health.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from fastapi.encoders import jsonable_encoder
2+
from fastapi.routing import APIRouter
3+
from starlette import status
4+
from starlette.responses import JSONResponse
5+
6+
from app.config import get_base_service_url, get_services
7+
from app.health.schema import HealthResponse, HealthStatus
8+
from app.utils import get_final_service_url, send_async_requests
9+
10+
health_route = APIRouter()
11+
12+
13+
@health_route.get(
14+
"/",
15+
summary="Health Check",
16+
description="Returns the health status of all the beacons.",
17+
response_model=HealthResponse,
18+
responses={
19+
status.HTTP_200_OK: {
20+
"description": "Service is healthy.",
21+
"content": {
22+
"application/health+json": {
23+
"example": [
24+
{
25+
"status": "pass",
26+
"service_id": "pdbe",
27+
"beacons_api_version": "2.7.0",
28+
"output": "",
29+
}
30+
]
31+
}
32+
},
33+
},
34+
status.HTTP_500_INTERNAL_SERVER_ERROR: {
35+
"description": "Service is down or unhealthy.",
36+
"content": {
37+
"application/health+json": {
38+
"example": [
39+
{
40+
"status": "fail",
41+
"service_id": "pdbe",
42+
"beacons_api_version": "2.7.0",
43+
"output": "Service is unhealthy, not able to "
44+
"reach the backend!",
45+
}
46+
]
47+
}
48+
},
49+
},
50+
},
51+
)
52+
async def health_check():
53+
"""
54+
Returns the health status of all the beacons.
55+
"""
56+
services = get_services(service_type="health")
57+
calls = []
58+
for service in services:
59+
base_url = get_base_service_url(service["provider"])
60+
final_url = get_final_service_url(base_url, service["accessPoint"])
61+
62+
calls.append(final_url)
63+
64+
result = await send_async_requests(calls)
65+
final_result = []
66+
67+
failed = False
68+
for beacons_response in result:
69+
if beacons_response.status_code != status.HTTP_200_OK:
70+
# If any service returns a non-200 status, we consider the
71+
# overall health check as failed
72+
failed = True
73+
for beacons_status in beacons_response.json():
74+
final_result.append(HealthStatus(**beacons_status))
75+
76+
if failed:
77+
return JSONResponse(
78+
media_type="application/health+json",
79+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
80+
content=jsonable_encoder(final_result),
81+
)
82+
return JSONResponse(
83+
media_type="application/health+json",
84+
status_code=status.HTTP_200_OK,
85+
content=jsonable_encoder(final_result),
86+
)

app/health/schema.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from enum import Enum
2+
from typing import List, Optional
3+
4+
from pydantic import BaseModel, Field
5+
6+
7+
# Health Endpoint Models
8+
class HealthStatusEnum(str, Enum):
9+
"""Health status enumeration based on the OpenAPI spec"""
10+
11+
PASS = "pass"
12+
WARN = "warn"
13+
FAIL = "fail"
14+
15+
16+
class HealthStatus(BaseModel):
17+
"""Individual service health status model for /health endpoint"""
18+
19+
status: HealthStatusEnum = Field(..., description="The status of the service")
20+
service_id: str = Field(
21+
...,
22+
description="The identifier of the service, corresponds to the provider ID "
23+
"in the registry",
24+
)
25+
beacons_api_version: str = Field(
26+
..., description="The version of the 3DBeacons API"
27+
)
28+
output: Optional[str] = Field(
29+
None, description="Raw error output, in case of 'fail' or 'warn' states"
30+
)
31+
32+
33+
class HealthResponse(BaseModel):
34+
"""Response model for /health endpoint - returns array of status objects"""
35+
36+
__root__: List[HealthStatus] = Field(
37+
..., description="List of service health statuses"
38+
)

app/sequence/schema.py

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from typing import List
1+
from enum import Enum
2+
from typing import List, Optional
23

3-
from pydantic import BaseModel
4+
from pydantic import BaseModel, Field
45

56
from app.constants import (
67
JOB_FAILED_ERROR_MESSAGE,
@@ -66,3 +67,236 @@ class SearchResults(BaseModel):
6667
current_page: int
6768
total_pages: int
6869
max_results_per_page: int
70+
71+
72+
class ChecksumType(str, Enum):
73+
"""Type of checksum enumeration"""
74+
75+
CRC64 = "CRC64"
76+
MD5 = "MD5"
77+
78+
79+
class ModelCategory(str, Enum):
80+
"""Model category enumeration"""
81+
82+
EXPERIMENTALLY_DETERMINED = "EXPERIMENTALLY DETERMINED"
83+
TEMPLATE_BASED = "TEMPLATE-BASED"
84+
AB_INITIO = "AB-INITIO"
85+
CONFORMATIONAL_ENSEMBLE = "CONFORMATIONAL ENSEMBLE"
86+
87+
88+
class ModelFormat(str, Enum):
89+
"""Model format enumeration"""
90+
91+
PDB = "PDB"
92+
MMCIF = "MMCIF"
93+
BCIF = "BCIF"
94+
95+
96+
class ModelType(str, Enum):
97+
"""Model type enumeration"""
98+
99+
ATOMIC = "ATOMIC"
100+
DUMMY = "DUMMY"
101+
MIX = "MIX"
102+
103+
104+
class ExperimentalMethod(str, Enum):
105+
"""Experimental method enumeration"""
106+
107+
ELECTRON_CRYSTALLOGRAPHY = "ELECTRON CRYSTALLOGRAPHY"
108+
ELECTRON_MICROSCOPY = "ELECTRON MICROSCOPY"
109+
EPR = "EPR"
110+
FIBER_DIFFRACTION = "FIBER DIFFRACTION"
111+
FLUORESCENCE_TRANSFER = "FLUORESCENCE TRANSFER"
112+
INFRARED_SPECTROSCOPY = "INFRARED SPECTROSCOPY"
113+
NEUTRON_DIFFRACTION = "NEUTRON DIFFRACTION"
114+
X_RAY_POWDER_DIFFRACTION = "X-RAY POWDER DIFFRACTION"
115+
SOLID_STATE_NMR = "SOLID-STATE NMR"
116+
SOLUTION_NMR = "SOLUTION NMR"
117+
X_RAY_SOLUTION_SCATTERING = "X-RAY SOLUTION SCATTERING"
118+
THEORETICAL_MODEL = "THEORETICAL MODEL"
119+
X_RAY_DIFFRACTION = "X-RAY DIFFRACTION"
120+
HYBRID = "HYBRID"
121+
122+
123+
class ConfidenceType(str, Enum):
124+
"""Confidence type enumeration"""
125+
126+
PLDDT = "pLDDT"
127+
QMEANDISCO = "QMEANDisCo"
128+
IPTM_PTM = "ipTM+pTM"
129+
130+
131+
class OligomericState(str, Enum):
132+
"""Oligomeric state enumeration"""
133+
134+
MONOMER = "MONOMER"
135+
HOMODIMER = "HOMODIMER"
136+
HETERODIMER = "HETERODIMER"
137+
HOMO_OLIGOMER = "HOMO-OLIGOMER"
138+
HETERO_OLIGOMER = "HETERO-OLIGOMER"
139+
140+
141+
class EntityType(str, Enum):
142+
"""Entity type enumeration"""
143+
144+
BRANCHED = "BRANCHED"
145+
MACROLIDE = "MACROLIDE"
146+
NON_POLYMER = "NON-POLYMER"
147+
POLYMER = "POLYMER"
148+
WATER = "WATER"
149+
150+
151+
class EntityPolyType(str, Enum):
152+
"""Entity poly type enumeration"""
153+
154+
CYCLIC_PSEUDO_PEPTIDE = "CYCLIC-PSEUDO-PEPTIDE"
155+
PEPTIDE_NUCLEIC_ACID = "PEPTIDE NUCLEIC ACID"
156+
POLYDEOXYRIBONUCLEOTIDE = "POLYDEOXYRIBONUCLEOTIDE"
157+
POLYDEOXYRIBONUCLEOTIDE_POLYRIBONUCLEOTIDE_HYBRID = (
158+
"POLYDEOXYRIBONUCLEOTIDE/POLYRIBONUCLEOTIDE HYBRID"
159+
)
160+
POLYPEPTIDE_D = "POLYPEPTIDE(D)"
161+
POLYPEPTIDE_L = "POLYPEPTIDE(L)"
162+
POLYRIBONUCLEOTIDE = "POLYRIBONUCLEOTIDE"
163+
OTHER = "OTHER"
164+
165+
166+
class IdentifierCategory(str, Enum):
167+
"""Identifier category enumeration"""
168+
169+
UNIPROT = "UNIPROT"
170+
RFAM = "RFAM"
171+
CCD = "CCD"
172+
SMILES = "SMILES"
173+
INCHI = "INCHI"
174+
INCHIKEY = "INCHIKEY"
175+
176+
177+
class SequenceIdType(str, Enum):
178+
SEQUENCE = "sequence"
179+
CRC64 = "crc64"
180+
MD5 = "md5"
181+
182+
183+
class Entity(BaseModel):
184+
"""Molecular entity in the model"""
185+
186+
entity_type: EntityType = Field(..., description="Type of the molecular entity")
187+
entity_poly_type: Optional[EntityPolyType] = Field(
188+
None, description="Type of the molecular entity polymer"
189+
)
190+
identifier: Optional[str] = Field(None, description="Identifier of the molecule")
191+
identifier_category: Optional[IdentifierCategory] = Field(
192+
None, description="Category of the identifier"
193+
)
194+
description: str = Field(..., description="Textual label of the molecule")
195+
chain_ids: List[str] = Field(
196+
..., description="List of chain identifiers of the molecule"
197+
)
198+
199+
200+
class SummaryItems(BaseModel):
201+
"""Summary items for a structure"""
202+
203+
model_identifier: str = Field(
204+
..., description="Identifier of the model, such as PDB id"
205+
)
206+
model_category: ModelCategory = Field(..., description="Category of the model")
207+
model_url: str = Field(..., description="URL of the model coordinates")
208+
model_format: ModelFormat = Field(..., description="File format of the coordinates")
209+
model_type: Optional[ModelType] = Field(
210+
None,
211+
description="Defines if coordinates are atomic-level or contain dummy atoms",
212+
)
213+
model_page_url: Optional[str] = Field(
214+
None, description="URL of a web page showing the model"
215+
)
216+
provider: str = Field(..., description="Name of the model provider")
217+
number_of_conformers: Optional[float] = Field(
218+
None, description="Number of conformers in a conformational ensemble"
219+
)
220+
ensemble_sample_url: Optional[str] = Field(
221+
None, description="URL of a sample of conformations from ensemble"
222+
)
223+
ensemble_sample_format: Optional[ModelFormat] = Field(
224+
None, description="File format of the sample coordinates"
225+
)
226+
created: str = Field(
227+
...,
228+
description="Date of release of model generation in the format of YYYY-MM-DD",
229+
example="2021-12-21",
230+
)
231+
sequence_identity: float = Field(
232+
..., ge=0, le=1, description="Sequence identity of model to UniProt sequence"
233+
)
234+
coverage: float = Field(
235+
..., ge=0, le=1, description="Fraction of UniProt sequence covered by the model"
236+
)
237+
experimental_method: Optional[ExperimentalMethod] = Field(
238+
None, description="Experimental method used to determine structure"
239+
)
240+
resolution: Optional[float] = Field(
241+
None, gt=0, description="Resolution of the model in Angstrom"
242+
)
243+
confidence_type: Optional[ConfidenceType] = Field(
244+
None, description="Type of confidence measure"
245+
)
246+
confidence_version: Optional[str] = Field(
247+
None, description="Version of confidence measure software"
248+
)
249+
confidence_avg_local_score: Optional[float] = Field(
250+
None, description="Average of confidence measures"
251+
)
252+
oligomeric_state: Optional[OligomericState] = Field(
253+
None, description="Oligomeric state of the model"
254+
)
255+
oligomeric_state_confidence: Optional[float] = Field(
256+
None, description="Confidence in oligomeric state"
257+
)
258+
preferred_assembly_id: Optional[str] = Field(
259+
None, description="Identifier of preferred assembly"
260+
)
261+
entities: List[Entity] = Field(
262+
..., description="List of molecular entities in the model"
263+
)
264+
265+
266+
class SequenceOverview(BaseModel):
267+
"""Sequence overview containing summary items"""
268+
269+
summary: SummaryItems = Field(
270+
..., description="Summary information for the structure"
271+
)
272+
273+
274+
class Entry(BaseModel):
275+
"""Entry information for sequence summary"""
276+
277+
sequence: str = Field(..., description="The protein sequence")
278+
checksum: str = Field(..., description="CRC64 or MD5 checksum of the sequence")
279+
checksum_type: ChecksumType = Field(..., description="Type of the checksum")
280+
281+
282+
class SequenceSummary(BaseModel):
283+
"""Main response model for /sequence/summary endpoint"""
284+
285+
entry: Entry = Field(
286+
..., description="Entry information including sequence and checksum"
287+
)
288+
structures: List[SequenceOverview] = Field(
289+
..., description="List of available structures"
290+
)
291+
292+
293+
# Request models for the endpoint
294+
class SequenceSummaryRequest(BaseModel):
295+
"""Request parameters for /sequence/summary endpoint"""
296+
297+
298+
# Response models
299+
class SequenceSummaryResponse(BaseModel):
300+
"""Response wrapper for successful requests"""
301+
302+
data: SequenceSummary

0 commit comments

Comments
 (0)