44import requests
55from functools import lru_cache , reduce
66
7- from typing import List , Union , TextIO , Optional , Dict
7+ from typing import List , Union , TextIO , Optional , Dict , Set
88
99from linkml_runtime .linkml_model import PermissibleValueText
1010from linkml_runtime .utils .schemaview import SchemaView
2323
2424LATEST_BIOLINK_RELEASE = "4.2.2"
2525
26- REMOTE_PATH = f"https://raw.githubusercontent.com/biolink/biolink-model/v{ LATEST_BIOLINK_RELEASE } /biolink-model.yaml "
27- PREDICATE_MAP = f"https://raw.githubusercontent.com/ biolink/biolink -model/v { LATEST_BIOLINK_RELEASE } /predicate_mapping .yaml"
28-
26+ BIOLINK_MODEL_RAW_BASEURL = f"https://raw.githubusercontent.com/biolink/biolink-model/v{ LATEST_BIOLINK_RELEASE } /"
27+ REMOTE_PATH = f"{ BIOLINK_MODEL_RAW_BASEURL } biolink-model.yaml"
28+ PREDICATE_MAP = f" { BIOLINK_MODEL_RAW_BASEURL } predicate_mapping.yaml"
2929
3030NODE_PROPERTY = "node property"
3131ASSOCIATION_SLOT = "association slot"
@@ -402,6 +402,99 @@ def match_association(
402402 return False
403403 return True
404404
405+ _warning_msg_templates : Dict [str , str ] = {
406+
407+ "get_associations_subject_category" :
408+ "Could not find subject category elements:\n \t '{ids}'\n within the current Biolink Model release?" ,
409+
410+ "get_associations_object_category" :
411+ "Could not find object category elements:\n \t '{ids}'\n within the current Biolink Model release?" ,
412+
413+ "get_associations_predicate" :
414+ "Could not find predicate elements:\n \t '{ids}'\n within the current Biolink Model release?" ,
415+
416+ "get_associations_no_predicate_inverse" :
417+ "Predicates:\n \t '{ids}'\n are symmetric or lack an inverse, within the current Biolink Model release?" ,
418+
419+ "get_associations_missing_association" :
420+ "Associations:\n \t '{ids}'\n does not match any association class within the current Biolink Model release?" ,
421+
422+ "get_element_by_prefix_missing_element" :
423+ "No Biolink class found for the given curies:\n \t '{ids}'\n ...try 'get_element_by_mapping'?"
424+ }
425+
426+ @classmethod
427+ def _format_warning_msg (cls , context : str , identifiers : Set [str ]) -> str :
428+ """
429+ Method to format warning messages associated with a
430+ specified element denoted by 'identifier',
431+ triggering the warning within a given functional context.
432+
433+ Parameters
434+ ----------
435+ context: str
436+ Specific functional context for which the warning is being reported.
437+ identifiers: List[str]
438+ Specific element identifier targets about which the warning message is ussed.
439+
440+ Returns
441+ -------
442+ Formatted message string
443+ """
444+ # sanity check
445+ assert context in cls ._warning_msg_templates , f"Missing message template for context '{ context } '?"
446+
447+ template : str = cls ._warning_msg_templates [context ]
448+ identifiers_str = ", " .join (identifiers )
449+ return f"{ context } | { template .format (ids = identifiers_str )} "
450+
451+ # indexed list of identifiers captured in a given warning context
452+ _warning_id_catalog : Dict [str , Set [str ]] = {}
453+
454+ @classmethod
455+ def warning (cls , context : str , identifier : str ) -> None :
456+ """
457+ Method to log warnings in a specified context and
458+ associated with a specific element, denoted by 'identifier'.
459+
460+ Parameters
461+ ----------
462+ context: str
463+ Specific functional context for which the warning is being reported.
464+ identifier: str
465+ Specific element identifier target of the warning.
466+
467+ Returns
468+ -------
469+ None
470+ """
471+ if context not in cls ._warning_id_catalog :
472+ cls ._warning_id_catalog [context ] = set ()
473+ identifiers : Set [str ] = cls ._warning_id_catalog .get (context , [])
474+ identifiers .add (identifier )
475+
476+ @classmethod
477+ def clear_warnings (cls ) -> None :
478+ """
479+ Clears out all warnings captured since initial
480+ Toolkit usage or since last invocation of this method.
481+ Returns
482+ -------
483+ None
484+ """
485+ cls ._warning_id_catalog .clear ()
486+
487+ @classmethod
488+ def dump_warnings (cls ) -> str :
489+ """
490+ Dumps a flat list report by context of all warnings reported since
491+ Toolkit creation or since the last invocation of "clear_warnings'.
492+ """
493+ report : str = ""
494+ for context , identifiers in cls ._warning_id_catalog .items ():
495+ report += cls ._format_warning_msg (context = context , identifiers = identifiers )+ "\n \n "
496+ return report
497+
405498 def get_associations (
406499 self ,
407500 subject_categories : Optional [List [str ]] = None ,
@@ -458,9 +551,9 @@ def get_associations(
458551 for sc in subject_categories :
459552 sc_elem = self .get_element (sc )
460553 if not sc_elem :
461- logger .warning (
462- f"get_associations(): could not find subject category " +
463- f"element ' { str (sc )} ' in current Biolink Model release?"
554+ self .warning (
555+ context = "get_associations_subject_category" ,
556+ identifier = str (sc )
464557 )
465558 return []
466559 sc_formatted = format_element (sc_elem )
@@ -470,9 +563,9 @@ def get_associations(
470563 for oc in object_categories :
471564 oc_elem = self .get_element (oc )
472565 if not oc_elem :
473- logger .warning (
474- f"get_associations(): could not find object category " +
475- f"element ' { str (oc )} ' in current Biolink Model release?"
566+ self .warning (
567+ context = "get_associations_object_category" ,
568+ identifier = str (oc )
476569 )
477570 return []
478571 oc_formatted = format_element (oc_elem )
@@ -482,9 +575,9 @@ def get_associations(
482575 for pred in predicates :
483576 p_elem = self .get_element (pred )
484577 if not p_elem :
485- logger .warning (
486- f"get_associations(): could not find predicate " +
487- f"element ' { str (pred )} ' in current Biolink Model release?"
578+ self .warning (
579+ context = "get_associations_predicate" ,
580+ identifier = str (pred )
488581 )
489582 return []
490583 pred_formatted = format_element (p_elem )
@@ -501,9 +594,9 @@ def get_associations(
501594 inverse_p = self .get_inverse (p_elem .name )
502595 if not inverse_p :
503596 # might be a symmetrical predicate or a predicate lacking an inverse
504- logger .warning (
505- f"get_associations(): predicate ' { str ( p_elem . name ) } ' is symmetric or " +
506- "does not have an inverse, within the current Biolink Model release?"
597+ self .warning (
598+ context = "get_associations_no_predicate_inverse" ,
599+ identifier = str ( p_elem . name )
507600 )
508601 else :
509602 inverse_pred_formatted = format_element (inverse_p )
@@ -522,9 +615,9 @@ def get_associations(
522615 if not association :
523616 # TODO: unsure that this test is needed, since all
524617 # known association classes ought to have names?
525- logger .warning (
526- f"get_associations(): association name ' { str ( name ) } ' " +
527- f"does not match any element in the current Biolink Model release?"
618+ self .warning (
619+ context = "get_associations_missing_association" ,
620+ identifier = str ( name )
528621 )
529622 continue
530623
@@ -753,8 +846,9 @@ def get_permissible_value_parent(self, permissible_value: str, enum_name: str) -
753846 return parent
754847
755848 @lru_cache (CACHE_SIZE )
756- def get_permissible_value_children (self , permissible_value : str , enum_name : str ) -> Union [
757- str , PermissibleValueText , None ]:
849+ def get_permissible_value_children (
850+ self , permissible_value : str , enum_name : str
851+ ) -> Union [str , PermissibleValueText , None ]:
758852 """
759853 Gets the children of a permissible value in an enumeration.
760854
@@ -970,9 +1064,9 @@ def get_element(self, name: str) -> Optional[Element]:
9701064 if el .name .lower () == name .lower ():
9711065 element = el
9721066
973- if type (element ) == ClassDefinition and element .class_uri is None :
1067+ if isinstance (element , ClassDefinition ) and element .class_uri is None :
9741068 element .class_uri = format_element (element )
975- if type (element ) == SlotDefinition and element .slot_uri is None :
1069+ if isinstance (element , SlotDefinition ) and element .slot_uri is None :
9761070 element .slot_uri = format_element (element )
9771071 return element
9781072
@@ -1889,7 +1983,10 @@ def get_element_by_prefix(
18891983 if hasattr (element , 'id_prefixes' ) and prefix in element .id_prefixes :
18901984 categories .append (element .name )
18911985 if len (categories ) == 0 :
1892- logger .warning ("no biolink class found for the given curie: %s, try get_element_by_mapping?" , identifier )
1986+ self .warning (
1987+ context = "get_element_by_prefix_missing_element" ,
1988+ identifier = identifier
1989+ )
18931990
18941991 return categories
18951992
0 commit comments