Skip to content

Commit

Permalink
Non-destructive TRAPIResponseValidator.check_compliance_of_trapi_resp…
Browse files Browse the repository at this point in the history
…onse(response) validation of TRAPI Response JSON (release 3.8.3 bug removed Message)
  • Loading branch information
RichardBruskiewich committed Aug 16, 2023
1 parent ca6852b commit 3b3cde0
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

The Reasoner Validator package is evolving along with progress in TRAPI and Biolink Model standards within the NCATS Biomedical Knowledge Translator.


## 3.8.4
- Non-destructive TRAPIResponseValidator.check_compliance_of_trapi_response(response) validation of TRAPI Response JSON (release 3.8.3 bug removed Message)

## 3.8.3
- Fixed TRAPI release management to cache TRAPI GitHub code release and branch tags locally - in a **versions.yaml** file - to avoid Git API calling denial of service issues; Small **scripts/trapi_release.py** utility script provided to update the **versions.yaml** file, as periodically necessary.
- YAML file management tech debt cleaned up a tiny bit. Tweaked a couple of validation codes for this reason.
Expand Down
19 changes: 18 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "reasoner-validator"
version = "3.8.3"
version = "3.8.4"
description = "Validation tools for Reasoner API"
authors = [
"Richard Bruskiewich <richard.bruskiewich@delphinai.com>",
Expand Down Expand Up @@ -48,7 +48,7 @@ bmt = "^1.1.1"
# since 4.18.0 appeared to break something for the
# access and processing of JSON schemata
jsonschema = "~4.17.3"

dictdiffer = "^0.9.0"
PyYAML = "^6.0"
requests = "^2.28.1"
pydantic = "^1.10.11"
Expand Down
73 changes: 40 additions & 33 deletions reasoner_validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,48 +150,55 @@ def check_compliance_of_trapi_response(
# nothing more to validate?
return

# here, we split the message out from the Response
# checking along the way whether it is empty
# Here, we split the TRAPI Response.Message out from the other
# Response components, to allow for independent TRAPI Schema
# validation of those non-Message components versus the Message
# itself (checking along the way whether the Message is empty)
message: Optional[Dict] = response.pop('message')
if not message:
# This is valid TRAPI but reported as an error,
# and not interesting for further validation
if not self.suppress_empty_data_warnings:
self.report("error.trapi.response.message.empty")

# ... also, nothing more here to validate?
return

# we insert a stub to enable TRAPI validation
# of the remainder of the Response
# we insert a stub to enable TRAPI schema
# validation of the remainder of the Response
response['message'] = {}
if message:

# TRAPI JSON specified versions override default versions
if "schema_version" in response and response["schema_version"]:
if self.default_trapi:
self.trapi_version = get_latest_version(response["schema_version"])
# TRAPI JSON specified versions override default versions
if "schema_version" in response and response["schema_version"]:
if self.default_trapi:
self.trapi_version = get_latest_version(response["schema_version"])

if "biolink_version" in response and response["biolink_version"]:
if self.default_biolink:
self.bmt = get_biolink_model_toolkit(biolink_version=response["biolink_version"])
self.biolink_version = self.bmt.get_model_version()
if "biolink_version" in response and response["biolink_version"]:
if self.default_biolink:
self.bmt = get_biolink_model_toolkit(biolink_version=response["biolink_version"])
self.biolink_version = self.bmt.get_model_version()

response = self.sanitize_trapi_response(response)
response = self.sanitize_trapi_response(response)

self.is_valid_trapi_query(instance=response, component="Response")
if self.has_critical():
# we abort further processing here due to detected critical global validation errors?
return
self.is_valid_trapi_query(instance=response, component="Response")
if not self.has_critical():

status: Optional[str] = response['status'] if 'status' in response else None
if status and status not in ["OK", "Success", "QueryNotTraversable", "KPsNotAvailable"]:
self.report("warning.trapi.response.status.unknown", identifier=status)

status: Optional[str] = response['status'] if 'status' in response else None
if status and status not in ["OK", "Success", "QueryNotTraversable", "KPsNotAvailable"]:
self.report("warning.trapi.response.status.unknown", identifier=status)
# Sequentially validate the Query Graph, Knowledge Graph then validate
# the Results (which rely on the validity of the other two components)
elif self.has_valid_query_graph(message) and \
self.has_valid_knowledge_graph(message, max_kg_edges):
self.has_valid_results(message, max_results)

# else:
# we don't validate further if it has
# critical Response level errors

else:
# Empty Message is valid TRAPI but reported as an error
# in the validation and not interesting for further validation
if not self.suppress_empty_data_warnings:
self.report("error.trapi.response.message.empty")

# Sequentially validate the Query Graph, Knowledge Graph then validate
# the Results (which rely on the validity of the other two components)
elif self.has_valid_query_graph(message) and \
self.has_valid_knowledge_graph(message, max_kg_edges):
self.has_valid_results(message, max_results)
# Reconstitute the original Message
# to the Response before returning
response['message'] = message

@staticmethod
def sample_results(results: List, sample_size: int = 0) -> List:
Expand Down
14 changes: 13 additions & 1 deletion tests/test_response_validator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Unit tests for the generic (shared) components of the SRI Testing Framework
"""
from typing import Tuple, Dict
from typing import Dict
from sys import stderr

import logging
Expand All @@ -12,6 +12,8 @@

import pytest

from dictdiffer import diff

from reasoner_validator.trapi import TRAPI_1_3_0, TRAPI_1_4_2
from reasoner_validator.validator import TRAPIResponseValidator
from reasoner_validator.report import TRAPIGraphType
Expand Down Expand Up @@ -431,6 +433,16 @@ def test_sanitize_trapi_query(response: Dict):
}


# this unit test checks that the original response object is returned verbatim
def test_conservation_of_response_object():
validator: TRAPIResponseValidator = TRAPIResponseValidator()
input_response = deepcopy(_TEST_TRAPI_1_4_2_FULL_SAMPLE)
reference_response = deepcopy(_TEST_TRAPI_1_4_2_FULL_SAMPLE)
assert input_response == reference_response
validator.check_compliance_of_trapi_response(response=input_response)
assert not list(diff(input_response, reference_response))


@pytest.mark.parametrize(
"edges_limit,number_of_nodes_returned,number_of_edges_returned",
[
Expand Down

0 comments on commit 3b3cde0

Please sign in to comment.