Repository: biolink/biolink-model-toolkit

Issue:
Camel-case class names are not converted to CURIEs well
```python
>>> tk.get_descendants("transcript", formatted=True)
['biolink:Transcript', 'biolink:RNAProduct', 'biolink:Noncoding_RNAProduct', 'biolink:SiRNA', 'biolink:MicroRNA', 'biolink:RNAProductIsoform']
```

`biolink:Noncoding_RNAProduct` should not have an underscore.

Yet somehow, at the same time,

```python
>>> tk.get_element("noncoding RNA product").class_uri
'biolink:NoncodingRNAProduct'
```

These transformations should be unified, too.

Comments:

Patch:
diff --git a/bmt/utils.py b/bmt/utils.py
index f2ac488..f018ac7 100644
--- a/bmt/utils.py
+++ b/bmt/utils.py
@@ -4,6 +4,18 @@ import stringcase
 from linkml_runtime.linkml_model.meta import ClassDefinition, SlotDefinition, Element, ClassDefinitionName, \
     SlotDefinitionName, ElementName, TypeDefinition
 
+lowercase_pattern = re.compile(r"[a-zA-Z]*[a-z][a-zA-Z]*")
+underscore_pattern = re.compile(r"(?<!^)(?=[A-Z][a-z])")
+
+
+def from_camel(s: str, sep: str = " ") -> str:
+    underscored = underscore_pattern.sub(sep, s)
+    lowercased = lowercase_pattern.sub(
+        lambda match: match.group(0).lower(),
+        underscored,
+    )
+    return lowercased
+
 
 def camelcase_to_sentencecase(s: str) -> str:
     """
@@ -18,7 +30,7 @@ def camelcase_to_sentencecase(s: str) -> str:
     str
         string in sentence case form
     """
-    return stringcase.sentencecase(s).lower()
+    return from_camel(s, sep=" ")
 
 
 def snakecase_to_sentencecase(s: str) -> str:
@@ -65,7 +77,11 @@ def sentencecase_to_camelcase(s: str) -> str:
     str
         string in CamelCase form
     """
-    return stringcase.pascalcase(stringcase.snakecase(s))
+    return re.sub(
+        r"(?:^| )([a-zA-Z])",
+        lambda match: match.group(1).upper(),
+        s
+    )
 
 
 def format_element(element: Element) -> str:


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 3a7203f..5a15f71 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -1,6 +1,11 @@
+from bmt.toolkit import Toolkit
 import pytest
 
-from bmt.utils import parse_name
+from bmt.utils import parse_name, format_element, sentencecase_to_camelcase
+
+@pytest.fixture(scope="module")
+def toolkit():
+    return Toolkit()
 
 
 @pytest.mark.parametrize('query', [
@@ -18,8 +23,28 @@ from bmt.utils import parse_name
     ('RNA product', 'RNA product'),
     ('RNA Product', 'RNA Product'),
     ('Rna Product', 'Rna Product'),
+    ('biolink:RNAProduct', 'RNA product'),
 ])
 def test_parse_name(query):
     n = parse_name(query[0])
     assert n == query[1]
 
+
+@pytest.mark.parametrize('query', [
+    ('phenotypic feature', 'PhenotypicFeature'),
+    ('noncoding RNA product', 'NoncodingRNAProduct'),
+])
+def test_sentencecase_to_camelcase(query):
+    n = sentencecase_to_camelcase(query[0])
+    assert n == query[1]
+
+
+@pytest.mark.parametrize('query', [
+    ('related to', 'biolink:related_to'),
+    ('caused_by', 'biolink:caused_by'),
+    ('PhenotypicFeature', 'biolink:PhenotypicFeature'),
+    ('noncoding RNA product', 'biolink:NoncodingRNAProduct'),
+])
+def test_format_element(query, toolkit):
+    n = format_element(toolkit.get_element(query[0]))
+    assert n == query[1]