reasoner-validator 4.2.0__tar.gz → 4.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/PKG-INFO +1 -1
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/validation_codes_dictionary.md +6 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/pyproject.toml +1 -1
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/codes.yaml +8 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/utils/__init__.py +6 -6
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/validator.py +99 -48
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_ontology.py +27 -1
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_response_validator.py +42 -3
- reasoner_validator-4.2.2/tests/test_utils.py +77 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/LICENSE +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/README.md +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/.nojekyll +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/Makefile +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/conf.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/index.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/make.bat +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.biolink.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.report.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.trapi.mapping.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.trapi.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.validation_codes.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.versioning.rst +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/__init__.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/biolink/__init__.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/biolink/ontology.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/github.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/message.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/report.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/trapi/__init__.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/trapi/mapping.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/utils/http.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/validation_codes.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/versioning.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/versions.yaml +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/__init__.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/conftest.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_biolink_compliance_validation.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_data/patched_trapi_schema_v1.4.0-beta5.yaml +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_semver.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_trapi_versioning.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_validate.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_validation_report.py +0 -0
- {reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_workflows.py +0 -0
|
@@ -902,6 +902,12 @@
|
|
|
902
902
|
|
|
903
903
|
**Description:** An empty Knowledge Graph is allowed but merits a boundary response warning?
|
|
904
904
|
|
|
905
|
+
### warning.trapi.response.message.knowledge_graph.node.category.imprecise
|
|
906
|
+
|
|
907
|
+
**Message:** The category of the knowledge graph node matching an input node identifier is more generic than expected.
|
|
908
|
+
|
|
909
|
+
**Context:** identifier, expected_category, observed_categories
|
|
910
|
+
|
|
905
911
|
### warning.trapi.response.message.results.empty
|
|
906
912
|
|
|
907
913
|
**Message:** Response returned empty Message.results?
|
|
@@ -694,6 +694,14 @@ warning:
|
|
|
694
694
|
empty:
|
|
695
695
|
$message: "Response returned an empty Message Knowledge Graph?"
|
|
696
696
|
$description: "An empty Knowledge Graph is allowed but merits a boundary response warning?"
|
|
697
|
+
node:
|
|
698
|
+
category:
|
|
699
|
+
imprecise:
|
|
700
|
+
$message: "The category of the knowledge graph node matching an input node identifier is more generic than expected."
|
|
701
|
+
$context:
|
|
702
|
+
- identifier
|
|
703
|
+
- expected_category
|
|
704
|
+
- observed_categories
|
|
697
705
|
results:
|
|
698
706
|
empty:
|
|
699
707
|
$message: "Response returned empty Message.results?"
|
|
@@ -79,13 +79,9 @@ def get_aliases(identifier: str) -> List[str]:
|
|
|
79
79
|
# preferred_curie = preferred_id["identifier"]
|
|
80
80
|
# preferred_name = preferred_id["label"]
|
|
81
81
|
if "equivalent_identifiers" in clique.keys():
|
|
82
|
+
# Sanity check: returned aliases
|
|
83
|
+
# are all converted to upper case
|
|
82
84
|
aliases = [entry["identifier"] for entry in clique["equivalent_identifiers"]]
|
|
83
|
-
#
|
|
84
|
-
# Decided that it was a bad idea to remove
|
|
85
|
-
# the original identifier from the aliases...
|
|
86
|
-
# aliases.remove(identifier)
|
|
87
|
-
#
|
|
88
|
-
# print(dumps(aliases, indent=2))
|
|
89
85
|
else:
|
|
90
86
|
logging.warning(
|
|
91
87
|
f"get_aliases(): missing the 'equivalent identifiers' for the '{identifier}' clique?"
|
|
@@ -103,6 +99,10 @@ def get_aliases(identifier: str) -> List[str]:
|
|
|
103
99
|
# Logging various errors but always
|
|
104
100
|
# return the identifier as its own alias
|
|
105
101
|
aliases = [identifier]
|
|
102
|
+
elif identifier not in aliases:
|
|
103
|
+
# If the identifier itself is missing in the aliases, then
|
|
104
|
+
# could just be a letter case mismatch? See if you can correct for this...
|
|
105
|
+
aliases = [identifier if str(i).upper() == identifier.upper() else i for i in aliases]
|
|
106
106
|
|
|
107
107
|
return aliases
|
|
108
108
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional,
|
|
1
|
+
from typing import Optional, List, Dict, Set
|
|
2
2
|
|
|
3
3
|
from reasoner_validator.biolink import (
|
|
4
4
|
BiolinkValidator,
|
|
@@ -66,7 +66,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
66
66
|
|
|
67
67
|
def is_trapi_1_4_or_later(self) -> bool:
|
|
68
68
|
assert self.trapi_version
|
|
69
|
-
try: # try block ... Sanity check: in
|
|
69
|
+
try: # try block ... Sanity check: in testcase the trapi_version is somehow invalid?
|
|
70
70
|
target_major_version: SemVer = \
|
|
71
71
|
SemVer.from_string(self.trapi_version, core_fields=['major', 'minor'], ext_fields=[])
|
|
72
72
|
self._is_trapi_1_4_or_later = target_major_version >= TRAPI_1_4_0_SEMVER
|
|
@@ -402,14 +402,14 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
402
402
|
# there should be in the output
|
|
403
403
|
|
|
404
404
|
# object_ids = [r['node_bindings'][output_node_binding][0]['id'] for r in results_sample]
|
|
405
|
-
# if
|
|
405
|
+
# if testcase[output_element] not in object_ids:
|
|
406
406
|
# # The 'get_aliases' method uses the Translator NodeNormalizer to check if any of
|
|
407
|
-
# # the aliases of the
|
|
408
|
-
# output_aliases = get_aliases(
|
|
407
|
+
# # the aliases of the testcase[output_element] identifier are in the object_ids list
|
|
408
|
+
# output_aliases = get_aliases(testcase[output_element])
|
|
409
409
|
# if not any([alias == object_id for alias in output_aliases for object_id in object_ids]):
|
|
410
410
|
# validator.report(
|
|
411
411
|
# code=error.results.missing_bindings,
|
|
412
|
-
# identifier=
|
|
412
|
+
# identifier=testcase[output_element],
|
|
413
413
|
# output_node_binding=output_node_binding
|
|
414
414
|
# )
|
|
415
415
|
# # data_dump=f"Resolved aliases:\n{','.join(output_aliases)}\n" +
|
|
@@ -418,16 +418,39 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
418
418
|
# Only 'error' but not 'info' nor 'warning' messages invalidate the overall Message
|
|
419
419
|
return False if self.has_errors() else True
|
|
420
420
|
|
|
421
|
-
def
|
|
421
|
+
def category_matched(self, source_categories: List[str], target_categories: List[str]) -> bool:
|
|
422
422
|
"""
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
For each 'source' Biolink Model category given (list of CURIEs as strings?),
|
|
424
|
+
first get the union set of all parent (ancestral) categories, then check if
|
|
425
|
+
at least one of these categories is matched to the list of target categories.
|
|
426
|
+
|
|
427
|
+
:param source_categories: List[str], list of 'source' categories whose category hierarchy is to be matched.
|
|
428
|
+
:param target_categories: List[str], list of 'target' categories to be matched against 'source'
|
|
429
|
+
(or 'source parent' categories
|
|
430
|
+
:return: bool, True if a category match is found (as described above)
|
|
431
|
+
"""
|
|
432
|
+
source_category_set: Set = set()
|
|
433
|
+
# gather all the possible exact and ancestor (parent)
|
|
434
|
+
# categories of source_categories to match...
|
|
435
|
+
for source_category in source_categories:
|
|
436
|
+
source_category_set.update(self.bmt.get_ancestors(source_category, formatted=True, mixin=False))
|
|
437
|
+
# ...then check all the target categories against that source category set
|
|
438
|
+
return any([c in target_categories for c in source_category_set])
|
|
439
|
+
|
|
440
|
+
def testcase_node_found(self, target: str, identifiers: List[str], testcase: Dict, nodes: Dict) -> bool:
|
|
441
|
+
"""
|
|
442
|
+
Check for presence of the target identifier, with expected categories, in the "nodes" catalog.
|
|
443
|
+
If the identifier is found, and at least one KG node category is the expected category or a proper subclass
|
|
444
|
+
category of the test testcase category, then return True; if the node is found but the testcase category is not
|
|
445
|
+
the expected category but is a subclass category of the KG node categories (i.e. KG node categories
|
|
446
|
+
are too general), then return False. If the identifier is NOT found in the nodes list or
|
|
447
|
+
there is no overlap in the (expected or parent) testcase and node categories, then return False.
|
|
425
448
|
|
|
426
449
|
:param target: 'subject' or 'object'
|
|
427
450
|
:param identifiers: List of node (CURIE) identifiers to be checked in the "nodes" catalog
|
|
428
|
-
:param
|
|
451
|
+
:param testcase: Dict, full test testcase (to access the target node 'category')
|
|
429
452
|
:param nodes: Dict, nodes category indexed by node identifiers.
|
|
430
|
-
:return:
|
|
453
|
+
:return: bool, True if feasible node identifier and category match; False otherwise
|
|
431
454
|
"""
|
|
432
455
|
#
|
|
433
456
|
# "nodes": {
|
|
@@ -440,25 +463,53 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
440
463
|
assert target in ["subject", "object"]
|
|
441
464
|
for identifier in identifiers:
|
|
442
465
|
if identifier in nodes.keys():
|
|
443
|
-
# Found the target node identifier,
|
|
444
|
-
#
|
|
466
|
+
# Found the target node identifier, but is the expected category present?
|
|
467
|
+
# For this comparison, we assume that a specified node category plus all its
|
|
468
|
+
# parent categories, may be used to match the test testcase specified category.
|
|
445
469
|
node_details = nodes[identifier]
|
|
470
|
+
test_case_category: str = testcase[f"{target}_category"]
|
|
446
471
|
if "categories" in node_details:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
472
|
+
if self.category_matched(
|
|
473
|
+
source_categories=node_details["categories"],
|
|
474
|
+
target_categories=[test_case_category]
|
|
475
|
+
):
|
|
476
|
+
# The 'identifier' was present in the list of KG nodes, plus there was a match of the target
|
|
477
|
+
# testcase category either exactly to a specified one of the indicated KG node categories or to
|
|
478
|
+
# an ancestral ("parent") category of one of the node categories. This is a completely regular
|
|
479
|
+
# result. For example, if a KG node return is "biolink:Gene", but the testcase query is only
|
|
480
|
+
# expecting to see a "biolink:BiologicalEntity", however, since the identifier is matched
|
|
481
|
+
# exactly, since the node match is good with greater categorical precision than expected.
|
|
482
|
+
return True
|
|
483
|
+
elif self.category_matched(
|
|
484
|
+
source_categories=[test_case_category],
|
|
485
|
+
target_categories=node_details["categories"]
|
|
486
|
+
):
|
|
487
|
+
# The 'identifier' was present in the list of KG nodes;
|
|
488
|
+
# however, there was likely only a more general ("parent-level") category match
|
|
489
|
+
# of at least one of the KG node categories, to a parent category of the testcase category
|
|
490
|
+
# (since the 'exact match' match scenario was handled above). This is a less precise
|
|
491
|
+
# node match, hence we issue a warning. For example, maybe the KG node returned is only
|
|
492
|
+
# tagged as "biolink:NamedThing" but we are looking for a testcase with "biolink:Gene".
|
|
493
|
+
# Since the testcase identifier was matched exactly, we assume that the node is matched,
|
|
494
|
+
# but just with less than desired data type categorical precision.
|
|
495
|
+
self.report(
|
|
496
|
+
code="warning.trapi.response.message.knowledge_graph.node.category.imprecise",
|
|
497
|
+
identifier=identifier,
|
|
498
|
+
expected_category=test_case_category,
|
|
499
|
+
observed_categories=",".join(node_details["categories"])
|
|
500
|
+
)
|
|
450
501
|
return True
|
|
451
502
|
|
|
452
|
-
# Target node identifier or categories is missing
|
|
453
|
-
# or not annotated with the
|
|
503
|
+
# Target node identifier is missing from the list of KG nodes or categories is either missing
|
|
504
|
+
# or not annotated with any compatible category, hence, we deem the node effectively missing.
|
|
454
505
|
return False
|
|
455
506
|
|
|
456
507
|
@staticmethod
|
|
457
|
-
def
|
|
508
|
+
def testcase_edge_bindings(q_edge_ids: List[str], target_edge_id: str, data: Dict) -> bool:
|
|
458
509
|
"""
|
|
459
510
|
Check if target query edge id and knowledge graph edge id are in specified edge_bindings.
|
|
460
511
|
:rtype: object
|
|
461
|
-
:param q_edge_ids:
|
|
512
|
+
:param q_edge_ids: List[str], expected query edge identifiers in a matching result
|
|
462
513
|
:param target_edge_id: str, expected knowledge edge identifier in a matching result
|
|
463
514
|
:param data: TRAPI version-specific Response context from which the 'edge_bindings' may be retrieved
|
|
464
515
|
:return: True, if found
|
|
@@ -474,7 +525,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
474
525
|
return True
|
|
475
526
|
return False
|
|
476
527
|
|
|
477
|
-
def
|
|
528
|
+
def testcase_result_found(
|
|
478
529
|
self,
|
|
479
530
|
query_graph: Dict,
|
|
480
531
|
subject_id: str,
|
|
@@ -483,17 +534,17 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
483
534
|
results: List
|
|
484
535
|
) -> bool:
|
|
485
536
|
"""
|
|
486
|
-
Validate that test
|
|
537
|
+
Validate that test testcase S--P->O edge is found bound to the Results?
|
|
487
538
|
:param query_graph: Dict, query graph to which the results pertain
|
|
488
539
|
:param subject_id: str, subject node (CURIE) identifier
|
|
489
540
|
:param object_id: str, subject node (CURIE) identifier
|
|
490
541
|
:param edge_id: str, subject node (CURIE) identifier
|
|
491
542
|
:param results: List of (TRAPI-version specific) Result objects
|
|
492
|
-
:return: bool, True if
|
|
543
|
+
:return: bool, True if testcase S-P-O edge was found in the results
|
|
493
544
|
"""
|
|
494
|
-
assert query_graph, "
|
|
545
|
+
assert query_graph, "testcase_result_found() encountered an empty query graph"
|
|
495
546
|
q_edges: Dict = query_graph["edges"]
|
|
496
|
-
q_edge_ids =
|
|
547
|
+
q_edge_ids = list(q_edges.keys())
|
|
497
548
|
|
|
498
549
|
result_found: bool = False
|
|
499
550
|
result: Dict
|
|
@@ -573,7 +624,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
573
624
|
# the "analysis" key is at least present plus the objects themselves are 'well-formed'
|
|
574
625
|
analyses: List = result["analyses"]
|
|
575
626
|
for analysis in analyses:
|
|
576
|
-
edge_id_found: bool = self.
|
|
627
|
+
edge_id_found: bool = self.testcase_edge_bindings(q_edge_ids, edge_id, analysis)
|
|
577
628
|
if edge_id_found:
|
|
578
629
|
break
|
|
579
630
|
|
|
@@ -599,7 +650,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
599
650
|
# }
|
|
600
651
|
# ]
|
|
601
652
|
#
|
|
602
|
-
edge_id_found: bool = self.
|
|
653
|
+
edge_id_found: bool = self.testcase_edge_bindings(q_edge_ids, edge_id, result)
|
|
603
654
|
|
|
604
655
|
if subject_id_found and object_id_found and edge_id_found:
|
|
605
656
|
result_found = True
|
|
@@ -607,9 +658,9 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
607
658
|
|
|
608
659
|
return result_found
|
|
609
660
|
|
|
610
|
-
def
|
|
661
|
+
def testcase_input_found_in_response(
|
|
611
662
|
self,
|
|
612
|
-
|
|
663
|
+
testcase: Dict,
|
|
613
664
|
response: Dict
|
|
614
665
|
) -> bool:
|
|
615
666
|
"""
|
|
@@ -617,17 +668,17 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
617
668
|
in the Knowledge Graph of the TRAPI Response Message. This method assumes
|
|
618
669
|
that the TRAPI response is already generally validated as well-formed.
|
|
619
670
|
|
|
620
|
-
:param
|
|
671
|
+
:param testcase: Dict, input data test case
|
|
621
672
|
:param response: Dict, TRAPI Response whose message ought to contain the test case edge
|
|
622
673
|
:return: True if test case edge found; False otherwise
|
|
623
674
|
"""
|
|
624
675
|
# sanity checks
|
|
625
|
-
assert
|
|
626
|
-
assert response, "
|
|
627
|
-
assert "message" in response, "
|
|
676
|
+
assert testcase, "testcase_input_found_in_response(): Empty or missing test testcase data!"
|
|
677
|
+
assert response, "testcase_input_found_in_response(): Empty or missing TRAPI Response!"
|
|
678
|
+
assert "message" in response, "testcase_input_found_in_response(): TRAPI Response missing its Message component!"
|
|
628
679
|
|
|
629
680
|
#
|
|
630
|
-
#
|
|
681
|
+
# testcase: Dict parameter contains something like:
|
|
631
682
|
#
|
|
632
683
|
# idx: 0,
|
|
633
684
|
# subject_category: 'biolink:SmallMolecule',
|
|
@@ -644,7 +695,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
644
695
|
# Another sanity check - unlikely to be a problem since
|
|
645
696
|
# TRAPI schema validation should have picked it up since
|
|
646
697
|
# the TRAPI Message is "nullable: false" in the schema
|
|
647
|
-
assert message, "
|
|
698
|
+
assert message, "testcase_input_found_in_response(): Empty or missing TRAPI message component!"
|
|
648
699
|
|
|
649
700
|
message_found: bool = False
|
|
650
701
|
if "query_graph" not in message:
|
|
@@ -723,12 +774,12 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
723
774
|
# "CHEBI:6801": {"name": "metformin", "categories": ["biolink:Drug"]}
|
|
724
775
|
# }
|
|
725
776
|
#
|
|
726
|
-
# Check for
|
|
777
|
+
# Check for testcase 'subject_id' and 'object_id',
|
|
727
778
|
# with expected categories, in nodes catalog
|
|
728
779
|
nodes: Dict = knowledge_graph["nodes"]
|
|
729
|
-
subject_id =
|
|
780
|
+
subject_id: str = testcase["subject_id"] if "subject_id" in testcase else testcase["subject"]
|
|
730
781
|
subject_aliases = get_aliases(subject_id)
|
|
731
|
-
if not self.
|
|
782
|
+
if not self.testcase_node_found("subject", subject_aliases, testcase, nodes):
|
|
732
783
|
self.report(
|
|
733
784
|
code="error.trapi.response.message.knowledge_graph.node.missing",
|
|
734
785
|
identifier=subject_id,
|
|
@@ -736,9 +787,9 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
736
787
|
)
|
|
737
788
|
return False
|
|
738
789
|
|
|
739
|
-
object_id =
|
|
790
|
+
object_id: str = testcase["object_id"] if "object_id" in testcase else testcase["object"]
|
|
740
791
|
object_aliases = get_aliases(object_id)
|
|
741
|
-
if not self.
|
|
792
|
+
if not self.testcase_node_found("object", object_aliases, testcase, nodes):
|
|
742
793
|
self.report(
|
|
743
794
|
code="error.trapi.response.message.knowledge_graph.node.missing",
|
|
744
795
|
identifier=object_id,
|
|
@@ -757,10 +808,10 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
757
808
|
# }
|
|
758
809
|
#
|
|
759
810
|
# Check in the edges catalog for an edge containing
|
|
760
|
-
# the
|
|
811
|
+
# the testcase 'subject_id', 'predicate' and 'object_id'
|
|
761
812
|
edges: Dict = knowledge_graph["edges"]
|
|
762
813
|
|
|
763
|
-
predicate =
|
|
814
|
+
predicate = testcase["predicate"] if "predicate" in testcase else testcase["predicate_id"]
|
|
764
815
|
predicate_descendants: List[str]
|
|
765
816
|
inverse_predicate_descendants: List[str] = list() # may sometimes remain empty...
|
|
766
817
|
if self.validate_biolink():
|
|
@@ -769,7 +820,7 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
769
820
|
if inverse_predicate:
|
|
770
821
|
inverse_predicate_descendants = self.bmt.get_descendants(inverse_predicate, formatted=True)
|
|
771
822
|
else:
|
|
772
|
-
# simpler
|
|
823
|
+
# simpler testcase in which we are ignoring deep Biolink Model validation
|
|
773
824
|
predicate_descendants = [predicate]
|
|
774
825
|
|
|
775
826
|
edge_id_match: Optional[str] = None
|
|
@@ -794,10 +845,10 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
794
845
|
break
|
|
795
846
|
|
|
796
847
|
edge_id: str = \
|
|
797
|
-
f"{
|
|
798
|
-
f"({
|
|
848
|
+
f"{testcase['idx']}|" +\
|
|
849
|
+
f"({testcase['subject_id']}#{testcase['subject_category']})" + \
|
|
799
850
|
f"-[{predicate}]->" + \
|
|
800
|
-
f"({
|
|
851
|
+
f"({testcase['object_id']}#{testcase['object_category']})"
|
|
801
852
|
|
|
802
853
|
if edge_id_match is None:
|
|
803
854
|
self.report(
|
|
@@ -807,13 +858,13 @@ class TRAPIResponseValidator(BiolinkValidator):
|
|
|
807
858
|
return False
|
|
808
859
|
|
|
809
860
|
results: List = message["results"]
|
|
810
|
-
if not self.
|
|
861
|
+
if not self.testcase_result_found(query_graph, subject_match, object_match, edge_id_match, results):
|
|
811
862
|
self.report(
|
|
812
863
|
code="error.trapi.response.message.result.missing",
|
|
813
864
|
identifier=edge_id
|
|
814
865
|
)
|
|
815
866
|
return False
|
|
816
867
|
|
|
817
|
-
# By this point, the
|
|
868
|
+
# By this point, the testcase data assumed to be
|
|
818
869
|
# successfully validated in the TRAPI Response?
|
|
819
870
|
return True
|
|
@@ -7,7 +7,8 @@ import pytest
|
|
|
7
7
|
from reasoner_validator.utils.http import post_query
|
|
8
8
|
from reasoner_validator.biolink.ontology import (
|
|
9
9
|
ONTOLOGY_KP_TRAPI_SERVER,
|
|
10
|
-
NODE_NORMALIZER_SERVER
|
|
10
|
+
NODE_NORMALIZER_SERVER,
|
|
11
|
+
get_parent_concept
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
pytest_plugins = ('pytest_asyncio',)
|
|
@@ -77,3 +78,28 @@ async def test_post_query_to_node_normalization(curie: str, category: str):
|
|
|
77
78
|
assert "equivalent_identifiers" in result[curie]
|
|
78
79
|
assert len(result[curie]["equivalent_identifiers"])
|
|
79
80
|
assert category in result[curie]["type"]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize(
|
|
84
|
+
"curie,category,result",
|
|
85
|
+
[
|
|
86
|
+
( # Query 0 - chemical compounds are NOT in an ontology hierarchy
|
|
87
|
+
"CHEMBL.COMPOUND:CHEMBL2333026",
|
|
88
|
+
"biolink:SmallMolecule",
|
|
89
|
+
None
|
|
90
|
+
),
|
|
91
|
+
( # Query 1 - MONDO disease terms are in an ontology term hierarchy
|
|
92
|
+
"MONDO:0011027",
|
|
93
|
+
"biolink:Disease",
|
|
94
|
+
"MONDO:0015967"
|
|
95
|
+
),
|
|
96
|
+
( # Query 2 - HP phenotype terms are in an ontology term hierarchy
|
|
97
|
+
"HP:0040068", # "Abnormality of limb bone"
|
|
98
|
+
"biolink:PhenotypicFeature",
|
|
99
|
+
"HP:0000924" # Abnormality of the skeletal system
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
def test_get_parent_concept(curie: str, category: str, result: Optional[str]):
|
|
104
|
+
# Just use default Biolink Model release for this test
|
|
105
|
+
assert get_parent_concept(curie=curie, category=category, biolink_version=None) == result
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Unit tests for the generic (shared) components of the SRI Testing Framework
|
|
3
3
|
"""
|
|
4
|
-
from typing import Dict, Optional
|
|
4
|
+
from typing import List, Dict, Optional
|
|
5
5
|
from sys import stderr
|
|
6
6
|
|
|
7
7
|
import logging
|
|
@@ -1205,6 +1205,45 @@ def test_dump_report_of_biolink_model_compliance_of_trapi_response_with_errors(r
|
|
|
1205
1205
|
print("\n"+"="*80+"\n", file=stderr)
|
|
1206
1206
|
|
|
1207
1207
|
|
|
1208
|
+
@pytest.mark.parametrize(
|
|
1209
|
+
"source_categories,target_categories,outcome",
|
|
1210
|
+
[
|
|
1211
|
+
( # query 0 - empty lists don't have anything to match?
|
|
1212
|
+
[], [], False
|
|
1213
|
+
),
|
|
1214
|
+
( # query 1 - exact matches should be true
|
|
1215
|
+
["biolink:Gene"], ["biolink:Gene"], True
|
|
1216
|
+
),
|
|
1217
|
+
( # query 2 - biolink:Protein is **not** directly matched in the
|
|
1218
|
+
# 'source' hierarchy (even though its parents could overlap)
|
|
1219
|
+
["biolink:Gene"], ["biolink:Drug"], False
|
|
1220
|
+
),
|
|
1221
|
+
( # query 3 - but a 'target' category list with at least one exact
|
|
1222
|
+
# match of category to source will also be matched,
|
|
1223
|
+
# even if other target entries don't match
|
|
1224
|
+
["biolink:Gene"], ["biolink:Gene", "biolink:Drug"], True
|
|
1225
|
+
),
|
|
1226
|
+
( # query 4 - 'target' category matches at least one
|
|
1227
|
+
# parent of the list of 'source' categories
|
|
1228
|
+
["biolink:Gene"], ["biolink:BiologicalEntity"], True
|
|
1229
|
+
),
|
|
1230
|
+
( # query 5 - a 'target' category list matches at least
|
|
1231
|
+
# one match to a parent of the list of the
|
|
1232
|
+
# 'source' categories, will be matched,
|
|
1233
|
+
# even if other target entries don't match
|
|
1234
|
+
#
|
|
1235
|
+
["biolink:Gene"], ["biolink:BiologicalEntity", "biolink:Drug"], True
|
|
1236
|
+
)
|
|
1237
|
+
]
|
|
1238
|
+
)
|
|
1239
|
+
def test_category_matched(source_categories: List[str], target_categories: List[str], outcome: bool):
|
|
1240
|
+
validator = TRAPIResponseValidator()
|
|
1241
|
+
assert validator.category_matched(
|
|
1242
|
+
source_categories,
|
|
1243
|
+
target_categories,
|
|
1244
|
+
) is outcome
|
|
1245
|
+
|
|
1246
|
+
|
|
1208
1247
|
@pytest.mark.parametrize(
|
|
1209
1248
|
"case,response",
|
|
1210
1249
|
[
|
|
@@ -1215,7 +1254,7 @@ def test_dump_report_of_biolink_model_compliance_of_trapi_response_with_errors(r
|
|
|
1215
1254
|
def test_empty_case_input_found_in_response(case, response):
|
|
1216
1255
|
validator = TRAPIResponseValidator()
|
|
1217
1256
|
with pytest.raises(AssertionError):
|
|
1218
|
-
validator.
|
|
1257
|
+
validator.testcase_input_found_in_response(case, response)
|
|
1219
1258
|
|
|
1220
1259
|
|
|
1221
1260
|
#
|
|
@@ -1458,5 +1497,5 @@ SAMPLE_TEST_INCOMPLETE_RESULTS = [
|
|
|
1458
1497
|
)
|
|
1459
1498
|
def test_case_input_found_in_response(case, response, code):
|
|
1460
1499
|
validator = TRAPIResponseValidator()
|
|
1461
|
-
assert not validator.
|
|
1500
|
+
assert not validator.testcase_input_found_in_response(case, response) if code else True
|
|
1462
1501
|
check_messages(validator, code)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests against reasoner-validator utils
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from reasoner_validator.utils import is_curie, get_aliases, get_reference
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"identifier,outcome",
|
|
12
|
+
[
|
|
13
|
+
(None, False),
|
|
14
|
+
("foo:bar", True),
|
|
15
|
+
("not-a-curie", False),
|
|
16
|
+
]
|
|
17
|
+
)
|
|
18
|
+
def test_is_curie(identifier: str, outcome: bool):
|
|
19
|
+
assert is_curie(identifier) is outcome
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize(
|
|
23
|
+
"identifier",
|
|
24
|
+
[
|
|
25
|
+
None,
|
|
26
|
+
""
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
def test_get_aliases_of_empty_identifier(identifier):
|
|
30
|
+
with pytest.raises(RuntimeError):
|
|
31
|
+
get_aliases(identifier)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize(
|
|
35
|
+
"identifier,one_alias",
|
|
36
|
+
[
|
|
37
|
+
( # Special test for a CURIE for which
|
|
38
|
+
# NodeNormalization is known to
|
|
39
|
+
# return a lower case namespace
|
|
40
|
+
"ORPHANET:33110",
|
|
41
|
+
"MONDO:0011096"
|
|
42
|
+
),
|
|
43
|
+
( # Test that the alias search is also
|
|
44
|
+
# input namespace case-insensitive:
|
|
45
|
+
# A modification of case of the input
|
|
46
|
+
# identifier is still properly returned.
|
|
47
|
+
"Orphanet:33110",
|
|
48
|
+
"MONDO:0011096"
|
|
49
|
+
),
|
|
50
|
+
( # only CURIEs can be resolved to aliases
|
|
51
|
+
# but just logs a warning but returns the identifier
|
|
52
|
+
"not-a-curie",
|
|
53
|
+
""
|
|
54
|
+
),
|
|
55
|
+
]
|
|
56
|
+
)
|
|
57
|
+
def test_get_aliases(identifier: str, one_alias: str):
|
|
58
|
+
aliases: List[str] = get_aliases(identifier)
|
|
59
|
+
assert identifier in aliases
|
|
60
|
+
assert one_alias in aliases if one_alias in aliases else True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize(
|
|
64
|
+
"identifier,reference",
|
|
65
|
+
[
|
|
66
|
+
( # Valid CURIE has a reference
|
|
67
|
+
"ORPHANET:33110",
|
|
68
|
+
"33110"
|
|
69
|
+
),
|
|
70
|
+
( # Only CURIEs can be resolved to references
|
|
71
|
+
"not-a-curie",
|
|
72
|
+
None
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
def test_get_reference(identifier: str, reference: str):
|
|
77
|
+
assert get_reference(identifier) == reference
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.trapi.mapping.rst
RENAMED
|
File without changes
|
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.validation_codes.rst
RENAMED
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/docs/reasoner_validator.versioning.rst
RENAMED
|
File without changes
|
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/biolink/__init__.py
RENAMED
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/biolink/ontology.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/reasoner_validator/validation_codes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reasoner_validator-4.2.0 → reasoner_validator-4.2.2}/tests/test_biolink_compliance_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|