Repository: SynBioDex/tyto (language: python)

Issue:
Add PubChem support
It would be good to be able to retrieve substance names from PubChem via ID, and vice versa if possible.

For example, https://identifiers.org/pubchem.substance:24901740 is 'Water, sterile-filtered, BioReagent, suitable for cell culture'


Discussion / hints:
We want to be able to get both `pubchem.compound` (molecular definitions) and `pubchem.substance` (reagent supplies of compounds)

See: https://pubchemdocs.ncbi.nlm.nih.gov/rdf

Fix patch:
diff --git a/setup.py b/setup.py
index 7671072..e08afa1 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 from setuptools import setup, find_packages
 
 setup(name='tyto',
-      version='1.0',
+      version='1.1',
       description='Automatically generates Python symbols for ontology terms',
       python_requires='>=3.6',
       url='https://github.com/SynBioDex/tyto',
diff --git a/tyto/__init__.py b/tyto/__init__.py
index d56c40b..e13accd 100644
--- a/tyto/__init__.py
+++ b/tyto/__init__.py
@@ -1,5 +1,5 @@
 from .tyto import Ontology, URI, configure_cache_size
-from .endpoint import Ontobee, EBIOntologyLookupService
+from .endpoint import Ontobee, EBIOntologyLookupService, PubChemAPI
 from .sbo import SBO
 from .so import SO
 from .ncit import NCIT
@@ -8,3 +8,4 @@
 from .sbol2 import SBOL2
 from .sbol3 import SBOL3
 from .edam import EDAM
+from .pubchem import PubChem
diff --git a/tyto/endpoint/endpoint.py b/tyto/endpoint/endpoint.py
index ee07666..a1bf08b 100644
--- a/tyto/endpoint/endpoint.py
+++ b/tyto/endpoint/endpoint.py
@@ -162,8 +162,6 @@ def get_ontologies(self):
         n_ontologies = int(len(response) / 2)
         for uri, ontology_name in zip(response[:n_ontologies], response[n_ontologies:]):
             ontologies[uri] = ontology_name
-            print(ontology_name, uri)
-        print(len(response[:n_ontologies]), len(response[n_ontologies+1:]))
         return ontologies
 
 
@@ -418,6 +416,36 @@ def convert(self, response):
     def query(self, query):
         pass
 
+class PUG_REST(RESTEndpoint):
+
+    def __init__(self):
+        super().__init__('https://pubchem.ncbi.nlm.nih.gov/rest/pug/substance')
+
+    def get_term_by_uri(self, ontology: "Ontology", uri: str):
+        if 'https://identifiers.org/pubchem.substance:' in uri:
+            uri = uri.replace('https://identifiers.org/pubchem.substance:',
+                              'https://pubchem.ncbi.nlm.nih.gov/rest/pug/substance/sid/')
+        get_query = f'{uri}/synonyms/JSON'
+        response = requests.get(get_query)
+        if response.status_code == 200:
+            return response.json()['InformationList']['Information'][0]['Synonym'][0]
+        if response.status_code == 404:
+            return None
+        raise urllib.error.HTTPError(get_query, response.status_code, response.reason, response.headers, None)
+
+    def get_uri_by_term(self, ontology: "Ontology", term: str):
+        term = urllib.parse.quote(term)
+        get_query = f'https://pubchem.ncbi.nlm.nih.gov/rest/pug/substance/name/{term}/sids/JSON'
+        response = requests.get(get_query)
+        if response.status_code == 200:
+            response = response.json()
+            if not response:
+                return None
+            if len(response['IdentifierList']['SID']) > 1:
+                raise LookupError('Ambiguous term--more than one matching ID found')
+            return f"https://identifiers.org/pubchem.substance:{response['IdentifierList']['SID'][0]}"
+        raise urllib.error.HTTPError(get_query, response.status_code, response.reason, response.headers, None)
+
 
 Ontobee = OntobeeEndpoint()
 """Endpoint instance representing Ontobee. Ontobee is the default linked data server for most OBO Foundry library ontologies, but is also been used for many non-OBO ontologies. 
@@ -426,3 +454,5 @@ def query(self, query):
 EBIOntologyLookupService = EBIOntologyLookupServiceAPI()
 """The Ontology Lookup Service (OLS) is a repository for biomedical ontologies that aims to provide a single point of access to the latest ontology versions. Hosted by the European Bioinformatics Institute
 """
+
+PubChemAPI = PUG_REST()
diff --git a/tyto/pubchem.py b/tyto/pubchem.py
new file mode 100644
index 0000000..7b7cd7e
--- /dev/null
+++ b/tyto/pubchem.py
@@ -0,0 +1,4 @@
+from . import Ontology, PubChemAPI
+
+PubChem = Ontology(endpoints=[PubChemAPI])
+


Test patch:
diff --git a/test/test_ontology.py b/test/test_ontology.py
index adf5b8d..a4e8ce0 100644
--- a/test/test_ontology.py
+++ b/test/test_ontology.py
@@ -172,6 +172,15 @@ def test_sbol3(self):
         self.assertEqual(SBOL3.reverseComplement, 'http://sbols.org/v3#reverseComplement')
         self.assertEqual(SBOL3.Component, 'http://sbols.org/v3#Component')
 
+class TestPubChem(unittest.TestCase):
+
+    def test_pubchem(self):
+        self.assertEqual(PubChem['LUDOX(R) CL-X colloidal silica, 45 wt. % suspension in H2O'], 'https://identifiers.org/pubchem.substance:24866361')
+        self.assertEqual(PubChem.get_term_by_uri('https://identifiers.org/pubchem.substance:24866361'), 'LUDOX(R) CL-X colloidal silica, 45 wt. % suspension in H2O')
+        with self.assertRaises(LookupError):
+            # Ambiguous term matches many SIDs
+            PubChem['water']
+
 
 if __name__ == '__main__':
     unittest.main()