Repository: biolink/biolink-model-toolkit

Issue:
add "is_mixin" and "has_inverse"
These would be helpful functions to have while generating QC reports on the biolink-model.

Discussion:

Patch:
diff --git a/bmt/toolkit.py b/bmt/toolkit.py
index 121054d..7b18352 100644
--- a/bmt/toolkit.py
+++ b/bmt/toolkit.py
@@ -9,7 +9,7 @@ from bmt.utils import format_element, parse_name
 
 Url = str
 Path = str
-REMOTE_PATH = 'https://raw.githubusercontent.com/biolink/biolink-model/1.5.0/biolink-model.yaml'
+REMOTE_PATH = 'https://raw.githubusercontent.com/biolink/biolink-model/1.6.0/biolink-model.yaml'
 
 CACHE_SIZE = 1024
 
@@ -724,6 +724,47 @@ class Toolkit(object):
         """
         return 'related to' in self.get_ancestors(name)
 
+    @lru_cache(CACHE_SIZE)
+    def is_mixin(self, name: str) -> bool:
+        """
+        Determines whether the given name is the name of a mixin
+        in the Biolink Model. An element is a mixin if one of its properties is "is_mixin:true"
+
+        Parameters
+        ----------
+        name: str
+            The name or alias of an element in the Biolink Model
+
+        Returns
+        -------
+        bool
+            That the named element is a valid mixin in Biolink Model
+        """
+        element = self.get_element(name)
+        is_mixin = element.mixin if isinstance(element, Definition) else False
+        return is_mixin
+
+    @lru_cache(CACHE_SIZE)
+    def has_inverse(self, name: str) -> bool:
+        """
+        Determines whether the given name is a predicate and if that predicate has an inverse defined
+        in the Biolink Model. An element is a predicate if it descends from
+        `related to`
+
+        Parameters
+        ----------
+        name: str
+            The name or alias of an element in the Biolink Model
+
+        Returns
+        -------
+        bool
+            That the named element is a valid mixin in Biolink Model
+        """
+        element = self.get_element(name)
+        has_inverse = element.inverse if isinstance(element, SlotDefinition) else False
+        return bool(has_inverse)
+
     @lru_cache(CACHE_SIZE)
     def in_subset(self, name: str, subset: str) -> bool:
         """
diff --git a/requirements.txt b/requirements.txt
index 2dbe8e4..d30b955 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,4 @@ biolinkml>=1.5.10
 deprecation>=2.0.6
 stringcase>=1.0.0
 pbr
+pytest


Test patch:
diff --git a/tests/unit/test_toolkit.py b/tests/unit/test_toolkit.py
index daf92c7..b429c5e 100644
--- a/tests/unit/test_toolkit.py
+++ b/tests/unit/test_toolkit.py
@@ -96,6 +96,20 @@ def test_predicate():
     assert toolkit.is_predicate('causes')
 
 
+def test_mixin():
+    toolkit = Toolkit()
+    assert not toolkit.is_mixin('named thing')
+    assert toolkit.is_mixin('ontology class')
+    assert not toolkit.is_mixin('this_does_not_exist')
+
+
+def test_has_inverse():
+    toolkit = Toolkit()
+    assert not toolkit.has_inverse('contributor')
+    assert toolkit.has_inverse('superclass of')
+    assert not toolkit.has_inverse('this_does_not_exist')
+
+
 def test_category():
     toolkit = Toolkit()
     assert toolkit.is_category('named thing')