Repository: SynBioDex/tyto (language: python)

Issue:
OWL node-ids contaminating SO query results
This error was observed using tyto 1.2 on a Mac.

When evaluating `tyto.SO.deletion` there is an ambiguous term exception raised, because searching for deletion finds two different items: `http://purl.obolibrary.org/obo/SO_0000159` (as expected) and `nodeID://b426842858`, which is an owl Axiom:
```
    <owl:Axiom>
        <owl:annotatedSource rdf:resource="http://purl.obolibrary.org/obo/SO_0000159"/>
        <owl:annotatedProperty rdf:resource="http://www.geneontology.org/formats/oboInOwl#hasDbXref"/>
        <owl:annotatedTarget rdf:datatype="http://www.w3.org/2001/XMLSchema#string">loinc:LA6692-3</owl:annotatedTarget>
        <rdfs:label rdf:datatype="http://www.w3.org/2001/XMLSchema#string">Deletion</rdfs:label>
    </owl:Axiom>
```

There are two different things coming together to create this problem:
1. The query is being done case-insensitive, which is a deliberate design choice
2. SO has decided to include case-sensitive information in the ontology, which is not typical.



Discussion / hints:
Two possible ways to solve this problem:
- Make queries case-sensitive. This would not work in general, but might be good for some specific cases.
- Filter out anonymous node values (`nodeID://` URIs, which are not a recognized URI schema)

I think the second might be a good approach.
What about this?  First, attempt an exact, case-sensitive match first (leaving out the FILTER clause here: https://github.com/SynBioDex/tyto/blob/master/tyto/endpoint/endpoint.py#L77), followed by a case-insensitive match if the first one fails (including the FILTER clause)?  
Yes, I think that would work.

It could also go in the other order, starting case-insensitive and then falling back to case sensitive if there are multiple matches.

Fix patch:
diff --git a/tyto/endpoint/endpoint.py b/tyto/endpoint/endpoint.py
index d86335e..f19bda7 100644
--- a/tyto/endpoint/endpoint.py
+++ b/tyto/endpoint/endpoint.py
@@ -82,7 +82,25 @@ def get_uri_by_term(self, ontology: "Ontology", term: str) -> str:
         if not response:
             return None
         if len(response) > 1:
-            raise Exception(f'Ambiguous term {term}--more than one matching URI found')
+            # If response was ambiguous, try again with a case-sensitive query instead:
+            case_sensitive_query = '''
+                SELECT distinct ?uri
+                {{from_clause}}
+                WHERE
+                {{{{
+                    {{{{
+                        ?uri rdfs:label ?term
+                    }}}}
+                    FILTER(REGEX(?term, '{term}'))
+                }}}}
+                '''.format(term='^' + term.replace(' ', r'[\\-\\_\\s]') + '$')
+            response = self.query(ontology, case_sensitive_query, error_msg)
+            if not response:
+                # if it was ambiguous before, but got nothing now, then it's the wrong case
+                return None
+            if len(response) > 1:
+                # if it's still ambiguous, then raise an exception
+                raise Exception(f'Ambiguous term {term}--found multiple URIs {response}')
         response = response[0]
         return response
 


Test patch:
diff --git a/test/test_ontology.py b/test/test_ontology.py
index 200c0c7..d8dc57e 100644
--- a/test/test_ontology.py
+++ b/test/test_ontology.py
@@ -18,6 +18,9 @@ def test_SO(self):
         # Test deprecated identifier format
         term_b = SO.get_term_by_uri('http://identifiers.org/so/SO:0000110')
         self.assertEqual(term_a, term_b)
+        # test handling of OWL anonymous node problem (https://github.com/SynBioDex/tyto/issues/65)
+        term_c = SO.deletion
+        self.assertEqual(term_c, 'https://identifiers.org/SO:0000159')
 
     def test_getitem(self):
         # For terms with special characters, we must use subscripting