Skip to content

Commit

Permalink
removing unnecessary complexity for metadata - always load on startup…
Browse files Browse the repository at this point in the history
…, don't use Depends for no reason (much faster now),

added real example for trapi query endpoint (first edge from testing data),
changing deprecated example parameter to examples
  • Loading branch information
EvanDietzMorris committed Nov 16, 2023
1 parent a8607ac commit 9780eaa
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 88 deletions.
18 changes: 6 additions & 12 deletions PLATER/services/app_common.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"""FastAPI app."""
import json
from typing import Any, Dict, List

from fastapi import Body, Depends, FastAPI
from fastapi.responses import JSONResponse

from PLATER.models.models_trapi_1_0 import (
Message, ReasonerRequest, CypherRequest, SimpleSpecResponse, SimpleSpecElement,
Expand All @@ -13,18 +11,17 @@
from PLATER.services.util.graph_adapter import GraphInterface
from PLATER.services.util.metadata import GraphMetadata
from PLATER.services.util.overlay import Overlay
from PLATER.services.util.question import Question
from PLATER.services.util.api_utils import get_graph_interface, \
get_bl_helper, construct_open_api_schema, get_example, get_graph_metadata
get_bl_helper, construct_open_api_schema, get_example


APP_COMMON = FastAPI(openapi_url='/common/openapi.json', docs_url='/common/docs')

GRAPH_METADATA = GraphMetadata().get_metadata()

async def cypher(
request: CypherRequest = Body(
...,
example={"query": "MATCH (n) RETURN count(n)"},
examples=[{"query": "MATCH (n) RETURN count(n)"}],
),
graph_interface: GraphInterface = Depends(get_graph_interface),
) -> CypherResponse:
Expand Down Expand Up @@ -54,7 +51,7 @@ async def cypher(
async def overlay(
request: ReasonerRequest = Body(
...,
example={"message": get_example("overlay")},
examples=[{"message": get_example("overlay")}],
),
graph_interface: GraphInterface = Depends(get_graph_interface),
) -> Message:
Expand All @@ -77,12 +74,9 @@ async def overlay(
)


async def metadata(
graph_metadata: GraphMetadata = Depends(get_graph_metadata),
) -> Any:
async def metadata() -> Any:
"""Handle /metadata."""
response = await graph_metadata.get_metadata()
return response
return GRAPH_METADATA


APP_COMMON.add_api_route(
Expand Down
24 changes: 11 additions & 13 deletions PLATER/services/app_trapi_1_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,35 @@
from PLATER.services.util.metadata import GraphMetadata
from PLATER.services.util.question import Question
from PLATER.services.util.overlay import Overlay
from PLATER.services.util.api_utils import get_graph_interface, get_graph_metadata, construct_open_api_schema, get_example
from PLATER.services.util.api_utils import get_graph_interface, construct_open_api_schema, get_example

# Mount open api at /1.4/openapi.json
APP_TRAPI_1_4 = FastAPI(openapi_url="/openapi.json", docs_url="/docs", root_path='/1.4')

graph_metadata_reader = GraphMetadata()
META_KG_RESPONSE = jsonable_encoder(graph_metadata_reader.get_meta_kg())
SRI_TEST_DATA = graph_metadata_reader.get_sri_testing_data()
TRAPI_QUERY_EXAMPLE = graph_metadata_reader.get_example_qgraph()

async def get_meta_knowledge_graph(
graph_metadata: GraphMetadata = Depends(get_graph_metadata),
) -> JSONResponse:

async def get_meta_knowledge_graph() -> JSONResponse:
"""Handle /meta_knowledge_graph."""
meta_kg = await graph_metadata.get_meta_kg()
# we are intentionally returning a JSONResponse directly and skipping pydantic validation for speed
return JSONResponse(status_code=200,
content=jsonable_encoder(meta_kg),
content=META_KG_RESPONSE,
media_type="application/json")


async def get_sri_testing_data(
graph_metadata: GraphMetadata = Depends(get_graph_metadata),
):
async def get_sri_testing_data():
"""Handle /sri_testing_data."""
sri_test_data = await graph_metadata.get_sri_testing_data()
return sri_test_data
return SRI_TEST_DATA


async def reasoner_api(
response: Response,
request: ReasonerRequest = Body(
...,
# Works for now but in deployment would be replaced by a mount, specific to backend dataset
example=get_example("reasoner-trapi-1.3"),
examples=[TRAPI_QUERY_EXAMPLE],
),
graph_interface: GraphInterface = Depends(get_graph_interface),
):
Expand Down
4 changes: 0 additions & 4 deletions PLATER/services/util/api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ def get_graph_interface():
)


def get_graph_metadata():
return GraphMetadata()


def get_bl_helper():
"""Get Biolink helper."""
return BLHelper(config.get('BL_HOST', 'https://bl-lookup-sri.renci.org'))
Expand Down
148 changes: 89 additions & 59 deletions PLATER/services/util/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,73 +4,103 @@


class GraphMetadata:
"""
Singleton class for retrieving metadata
"""
class _GraphMetadata:

def __init__(self):
self.metadata = None
self.meta_kg = None
self.sri_testing_data = None

async def get_metadata(self):
if not self.metadata:
self.retrieve_metadata()
return self.metadata

def retrieve_metadata(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'metadata.json')) as f:
self.metadata = json.load(f)
def __init__(self):
self.metadata = None
self.meta_kg = None
self.sri_testing_data = None

if not self.metadata:
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'about.json')) as f:
self.metadata = json.load(f)
def get_metadata(self):
if not self.metadata:
self.retrieve_metadata()
return self.metadata

async def get_meta_kg(self):
if not self.meta_kg:
self.retrieve_meta_kg()
return self.meta_kg
def retrieve_metadata(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'metadata.json')) as f:
self.metadata = json.load(f)

def retrieve_meta_kg(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'meta_knowledge_graph.json')) as f:
self.meta_kg = json.load(f)
if not self.metadata:
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'about.json')) as f:
self.metadata = json.load(f)

# we removed pydantic validation for the meta kg for performance reasons,
# (and because it is static and should be validated upstream),
# but this ensures required fields aren't missing from attributes, as can be the case currently
for node_type, node_properties in self.meta_kg['nodes'].items():
for attribute_info in node_properties['attributes']:
if not attribute_info['attribute_type_id']:
attribute_info['attribute_type_id'] = 'biolink:Attribute'
if not attribute_info['attribute_source']:
attribute_info['attribute_source'] = None
if not attribute_info['constraint_use']:
attribute_info['constraint_use'] = False
if not attribute_info['constraint_name']:
attribute_info['constraint_name'] = None
def get_meta_kg(self):
if not self.meta_kg:
self.retrieve_meta_kg()
return self.meta_kg

async def get_sri_testing_data(self):
if not self.sri_testing_data:
self.retrieve_sri_test_data()
return self.sri_testing_data
def retrieve_meta_kg(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'meta_knowledge_graph.json')) as f:
self.meta_kg = json.load(f)

def retrieve_sri_test_data(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'sri_testing_data.json')) as f:
self.sri_testing_data = json.load(f)
# we removed pydantic validation for the meta kg for performance reasons,
# (and because it is static and should be validated upstream),
# but this ensures required fields aren't missing from attributes, as can be the case currently
for node_type, node_properties in self.meta_kg['nodes'].items():
for attribute_info in node_properties['attributes']:
if not attribute_info['attribute_type_id']:
attribute_info['attribute_type_id'] = 'biolink:Attribute'
if not attribute_info['attribute_source']:
attribute_info['attribute_source'] = None
if not attribute_info['constraint_use']:
attribute_info['constraint_use'] = False
if not attribute_info['constraint_name']:
attribute_info['constraint_name'] = None

# version is technically not part of the spec anymore
# but this ensures validation with the model until it's removed
if 'version' not in self.sri_testing_data:
self.sri_testing_data['version'] = config.get('BL_VERSION')
def get_sri_testing_data(self):
if not self.sri_testing_data:
self.retrieve_sri_test_data()
return self.sri_testing_data

instance = None
def retrieve_sri_test_data(self):
with open(os.path.join(os.path.dirname(__file__), '..', '..', 'metadata', 'sri_testing_data.json')) as f:
self.sri_testing_data = json.load(f)

def __init__(self):
# create a new instance if not already created.
if not GraphMetadata.instance:
GraphMetadata.instance = GraphMetadata._GraphMetadata()
# version is technically not part of the spec anymore
# but this ensures validation with the model until it's removed
if 'version' not in self.sri_testing_data:
self.sri_testing_data['version'] = config.get('BL_VERSION')

def __getattr__(self, item):
# proxy function calls to the inner object.
return getattr(self.instance, item)
def get_example_qgraph(self):
sri_test_data = self.get_sri_testing_data()
if not sri_test_data['edges']:
return {'error': 'Could not generate example without edges in sri_testing_data.'}
test_edge = sri_test_data['edges'][0]
example_trapi = {
"message": {
"query_graph": {
"nodes": {
"n0": {
"categories": [
test_edge['subject_category']
],
"ids": [
test_edge['subject_id']
]
},
"n1": {
"categories": [
test_edge['object_category']
],
"ids": [
test_edge['object_id']
]
}
},
"edges": {
"e01": {
"subject": "n0",
"object": "n1",
"predicates": [
test_edge['predicate']
]
}
}
}
},
"workflow": [
{
"id": "lookup"
}
]
}
return example_trapi

0 comments on commit 9780eaa

Please sign in to comment.