Skip to content

Commit

Permalink
Merge pull request #165 from x-atlas-consortia/jas_jan2025
Browse files Browse the repository at this point in the history
Enhancements and bug fixes
  • Loading branch information
yuanzhou authored Jan 10, 2025
2 parents fefcf0e + d2115dd commit 9305e8b
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 31 deletions.
6 changes: 3 additions & 3 deletions dd-api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ paths:
- name: term_id
in: path
required: true
description: The string to match. Subject to timeout.
description: The string to match. Subject to timeout. Should be URL-encoded for spaces (%20).
schema:
type: string
example: Breast cancer
Expand All @@ -950,7 +950,7 @@ paths:
- name: term_id
in: path
required: true
description: The string to match
description: The string to match. Should be URL-encoded for spaces (%20).
schema:
type: string
example: Breast cancer
Expand All @@ -974,7 +974,7 @@ paths:
parameters:
- name: sab
in: query
required: true
required: false
description: A source (SAB) to which to limit the response.
schema:
type: string
Expand Down
11 changes: 11 additions & 0 deletions src/ubkg_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from common_routes.sabs.sabs_controller import sabs_blueprint
from common_routes.sources.sources_controller import sources_blueprint

from utils.http_error_string import wrap_message

logging.basicConfig(format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s', level=logging.DEBUG,
datefmt='%Y-%m-%d %H:%M:%S')
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -102,6 +104,15 @@ def __init__(self, config, package_base_dir):
def index():
return "Hello! This is UBKG-API service :)"

@self.app.errorhandler(404)
# Custom 404 error handler.
def servererror(error):
return wrap_message(key='message', msg=error.description)

@self.app.errorhandler(500)
# Custom 500 error handler.
def servererror(error):
return wrap_message(key='error', msg=error.description)

####################################################################################################
## For local development/testing
Expand Down
24 changes: 16 additions & 8 deletions src/ubkg_api/common_routes/common_neo4j_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,15 +174,23 @@ def codes_code_id_codes_get_logic(neo4j_instance, code_id: str, sab: List[str])
def codes_code_id_concepts_get_logic(neo4j_instance, code_id: str) -> List[ConceptDetail]:
conceptdetails: List[ConceptDetail] = []

query: str = \
'WITH [$code_id] AS query' \
' MATCH (a:Code)<-[:CODE]-(b:Concept)' \
' WHERE a.CodeID IN query' \
' OPTIONAL MATCH (b)-[:PREF_TERM]->(c:Term)' \
' RETURN DISTINCT a.CodeID AS Code, b.CUI AS Concept, c.name as Prefterm' \
' ORDER BY Code ASC, Concept'
# Dec 2024 - replace in-line Cypher with loaded file.
# Load Cypher query from file.
query: str = loadquerystring(filename='codes_code_id_concepts.cypher')

# Filter by code_id.
query = query.replace('$code_id', f"'{code_id}'")

#query: str = \
#'WITH [$code_id] AS query' \
#' MATCH (a:Code)<-[:CODE]-(b:Concept)' \
#' WHERE a.CodeID IN query' \
#' OPTIONAL MATCH (b)-[:PREF_TERM]->(c:Term)' \
#' RETURN DISTINCT a.CodeID AS Code, b.CUI AS Concept, c.name as Prefterm' \
#' ORDER BY Code ASC, Concept'

with neo4j_instance.driver.session() as session:
recds: neo4j.Result = session.run(query, code_id=code_id)
recds: neo4j.Result = session.run(query)
for record in recds:
try:
conceptdetail: ConceptDetail = ConceptDetail(record.get('Concept'),
Expand Down
6 changes: 3 additions & 3 deletions src/ubkg_api/common_routes/concepts/concepts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from utils.http_error_string import get_404_error_string, validate_query_parameter_names, \
validate_parameter_value_in_enum, validate_required_parameters, validate_parameter_is_numeric, \
validate_parameter_is_nonnegative, validate_parameter_range_order, check_payload_size, \
check_neo4j_version_compatibility,check_max_mindepth
check_neo4j_version_compatibility,check_max_mindepth,wrap_message
# Functions to format query parameters for use in Cypher queries
from utils.http_parameter import parameter_as_list, set_default_minimum, set_default_maximum
# Functions common to paths routes
Expand Down Expand Up @@ -489,8 +489,8 @@ def concepts_paths_subraphs_sequential_get(concept_id=None):
relsabs = []
for rs in relsequence:
if not ':' in rs:
err = f'Invalid parameter value: {rs}. Format relationships as <SAB>:<relationship_type>'
return make_response(err, 400)
err = f'Invalid parameter value for \'relsequence\': {rs}. Format relationships as <SAB>:<relationship_type>'
return make_response(wrap_message(key="message", msg=err), 400)

relsabs.append(rs.split(':')[0].upper())
reltypes.append(rs.split(':')[1])
Expand Down
9 changes: 9 additions & 0 deletions src/ubkg_api/cypher/codes_code_id_concepts.cypher
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Used in the codes/{code_id}/concepts endpoint

// December 2024 - Changed to return only those concepts with a linked preferred term.

WITH $code_id AS query
MATCH (:Term)<-[d]-(a:Code)<-[:CODE]-(b:Concept)-[:PREF_TERM]->(c:Term)
WHERE ((a.CodeID = query) AND (b.CUI = d.CUI))
RETURN DISTINCT a.CodeID AS Code, b.CUI AS Concept, c.name as Prefterm
ORDER BY Code ASC, Concept
6 changes: 4 additions & 2 deletions src/ubkg_api/cypher/sources.cypher
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ CALL
CALL
{
WITH CUISource
OPTIONAL MATCH (pSource:Concept)-[:has_citation]->(p:Concept)-[:CODE]->(c:Code)-[r:PT]->(t:Term)
MATCH (pSource:Concept)-[:has_citation]->(p:Concept)-[:CODE]->(c:Code)-[r:PT]->(t:Term)
WHERE pSource.CUI=CUISource
AND r.CUI = p.CUI
RETURN COLLECT(DISTINCT {PMID:split(t.name,':')[1], url:'https://pubmed.ncbi.nlm.nih.gov/'+split(t.name,':')[1]}) AS citations
RETURN COLLECT({pmid:CASE WHEN split(t.name,':')[0]='PMID' THEN split(t.name,':')[1] END,
url:CASE WHEN split(t.name,':')[0]<>'PMID' THEN t.name ELSE 'https://pubmed.ncbi.nlm.nih.gov/'+split(t.name,':')[1] END}) AS citations
}

// ETL command
CALL
{
Expand Down
39 changes: 27 additions & 12 deletions src/ubkg_api/utils/http_error_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

from flask import request

def wrap_message(key:str, msg:str) ->dict:
"""
Wraps a return message string in JSON format.
"""

return {key: msg}

def format_request_path(custom_err=None):
"""
Expand Down Expand Up @@ -83,10 +89,10 @@ def get_404_error_string(prompt_string=None, custom_request_path=None, timeout=N
if timeout > 1:
errtimeout = errtimeout + "s"

err = err + f". Note that this endpoint is limited to an execution time of {errtimeout} " \
err = err + f". Note that this endpoint is limited to an execution time of {errtimeout}" \
f" to prevent timeout errors."

return err
return wrap_message(key="message", msg=err)


def get_number_agreement(list_items=None):
Expand Down Expand Up @@ -127,8 +133,9 @@ def validate_query_parameter_names(parameter_name_list=None) -> str:
if req not in parameter_name_list:
namelist = list_as_single_quoted_string(list_elements=parameter_name_list)
prompt = get_number_agreement(list_items=parameter_name_list)
return f"Invalid query parameter: '{req}'. The possible parameter name{prompt}: {namelist}. " \
err = f"Invalid query parameter: '{req}'. The possible parameter name{prompt}: {namelist}. " \
f"Refer to the SmartAPI documentation for this endpoint for more information."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -150,8 +157,9 @@ def validate_required_parameters(required_parameter_list=None) -> str:
if param not in request.args:
namelist = list_as_single_quoted_string(list_elements=required_parameter_list)
prompt = get_number_agreement(list_items=required_parameter_list)
return f"Missing query parameter: '{param}'. The required parameter{prompt}: {namelist}. " \
err = f"Missing query parameter: '{param}'. The required parameter{prompt}: {namelist}. " \
f"Refer to the SmartAPI documentation for this endpoint for more information."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -176,8 +184,9 @@ def validate_parameter_value_in_enum(param_name=None, param_value=None, enum_lis
if param_value not in enum_list:
namelist = list_as_single_quoted_string(list_elements=enum_list)
prompt = get_number_agreement(enum_list)
return f"Invalid value for parameter: '{param_name}'. The possible parameter value{prompt}: {namelist}. " \
err = f"Invalid value for parameter: '{param_name}'. The possible parameter value{prompt}: {namelist}. " \
f"Refer to the SmartAPI documentation for this endpoint for more information."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -193,7 +202,8 @@ def validate_parameter_is_numeric(param_name=None, param_value: str = '') -> str
"""

if not param_value.lstrip('-').isnumeric():
return f"Invalid value ({param_value}) for parameter '{param_name}'. The parameter must be numeric."
err = f"Invalid value ({param_value}) for parameter '{param_name}'. The parameter must be numeric."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -215,8 +225,8 @@ def validate_parameter_is_nonnegative(param_name=None, param_value: str = '') ->
return err

if int(param_value) < 0:
return f"Invalid value ({param_value}) for parameter '{param_name}'. The parameter cannot be negative."

err = f"Invalid value ({param_value}) for parameter '{param_name}'. The parameter cannot be negative."
return wrap_message(key="message", msg=err)
return "ok"


Expand All @@ -233,7 +243,8 @@ def validate_parameter_range_order(min_name: str, min_value: str, max_name: str,
"""

if int(min_value) > int(max_value):
return f"Invalid parameter values: '{min_name}' ({min_value}) greater than '{max_name}' ({max_value}). "
err = f"Invalid parameter values: '{min_name}' ({min_value}) greater than '{max_name}' ({max_value}). "
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -247,9 +258,10 @@ def check_payload_size(payload: str, max_payload_size: int) -> str:

payload_size = len(str(payload))
if payload_size > max_payload_size:
return f"The size of the response to the endpoint with the specified parameters " \
err = f"The size of the response to the endpoint with the specified parameters " \
f"({int(payload_size)/1024} MB) exceeds the payload limit" \
f" of {int(max_payload_size)/1024} MB."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -269,7 +281,8 @@ def check_neo4j_version_compatibility(query_version: str, instance_version: str)
int_query_version = int(query_version.replace('.', ''))

if int_instance_version < int_query_version:
return f"This functionality requires at least version {query_version} of neo4j."
err = f"This functionality requires at least version {query_version} of neo4j."
return wrap_message(key="message", msg=err)

return "ok"

Expand All @@ -281,6 +294,8 @@ def check_max_mindepth(mindepth: int, max_mindepth: int) -> str:
"""
if mindepth > max_mindepth:
return f"The maximum value of 'mindepth' for this endpoint is {max_mindepth}. " \
err = f"The maximum value of 'mindepth' for this endpoint is {max_mindepth}. " \
f"Larger values of 'mindepth' result in queries that will exceed the server timeout."
return wrap_message(key="message", msg=err)

return "ok"
6 changes: 3 additions & 3 deletions ubkg-api-spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ paths:
- name: term_id
in: path
required: true
description: The string to match. Subject to timeout.
description: The string to match. Subject to timeout. Should be URL-encoded for spaces (%20).
schema:
type: string
example: Breast cancer
Expand All @@ -948,7 +948,7 @@ paths:
- name: term_id
in: path
required: true
description: The string to match
description: The string to match. Should be URL-encoded for spaces (%20).
schema:
type: string
example: Breast cancer
Expand All @@ -972,7 +972,7 @@ paths:
parameters:
- name: sab
in: query
required: true
required: false
description: A source (SAB) to which to limit the response.
schema:
type: string
Expand Down

0 comments on commit 9305e8b

Please sign in to comment.