88- ChEMBL
99- ChEBI
1010- PubChem
11+ - CAS Common Chemistry (requires the ``CAS_API_KEY`` environment variable)
1112
1213.. note::
1314 This file is meant to be used by automated workflows.
1718import os
1819import re
1920import sys
21+ import urllib .parse
2022import urllib .request
2123from html import unescape
2224
@@ -91,6 +93,43 @@ def get_chebi(chebi_id):
9193 return {}
9294
9395
96+ def get_metabolights (chebi_id ):
97+ # MetaboLights reference compounds use the accession MTBLC<chebi numeric id>.
98+ # Return the identifier only if the compound exists in MetaboLights.
99+ if not chebi_id :
100+ return ""
101+ mtbl_id = f"MTBLC{ chebi_id } "
102+ if check_api (f"https://www.ebi.ac.uk/metabolights/ws/compounds/{ mtbl_id } " ):
103+ return mtbl_id
104+ return ""
105+
106+
107+ def get_cas (inchikey ):
108+ # CAS Registry Numbers are not exposed by UniChem. They can be retrieved from
109+ # CAS Common Chemistry, which requires an API token supplied via the
110+ # CAS_API_KEY environment variable. Returns "" when the token is missing,
111+ # the service is unreachable, or no match is found.
112+ if not inchikey :
113+ return ""
114+ api_key = os .environ .get ("CAS_API_KEY" )
115+ if not api_key :
116+ return ""
117+ # CAS Common Chemistry requires field-qualified queries; a bare InChIKey
118+ # does not match, whereas "InChIKey=<value>" does.
119+ query = urllib .parse .quote (f"InChIKey={ inchikey } " )
120+ url = f"https://commonchemistry.cas.org/api/search?q={ query } "
121+ try :
122+ req = urllib .request .Request (url , headers = {"X-Api-Key" : api_key })
123+ with urllib .request .urlopen (req , timeout = 5 ) as response :
124+ if response .status == 200 :
125+ results = json .loads (response .read ().decode ("utf-8" )).get ("results" , [])
126+ if results :
127+ return results [0 ].get ("rn" , "" ) or ""
128+ except Exception :
129+ pass
130+ return ""
131+
132+
94133def get_unichem (inchikey ):
95134 url = "https://www.ebi.ac.uk/unichem/api/v1/compounds"
96135 if check_api ("https://www.ebi.ac.uk/unichem/api/v1/sources" ):
@@ -117,8 +156,9 @@ def extract_sameas(sources):
117156 "lipidmaps" : "lipidmaps" ,
118157 "metabolights" : "metabolights" ,
119158 "swisslipids" : "slm" ,
120- "pdb" : "pdb.ligand" ,
121- "unii" : "unii" ,
159+ "rcsb_pdb" : "pdb.ligand" ,
160+ "pdbe" : "pdb.ligand" ,
161+ "fdasrs" : "unii" ,
122162 "cas" : "cas" ,
123163 }
124164 result = {}
@@ -127,7 +167,10 @@ def extract_sameas(sources):
127167 if prefix :
128168 value = src ["compoundId" ]
129169 if prefix == "ChEBI" :
130- value = f"CHEBI:{ value } " if value else ""
170+ if value :
171+ value = value if str (value ).startswith ("CHEBI:" ) else f"CHEBI:{ value } "
172+ else :
173+ value = ""
131174 elif prefix == "pubchem.compound" :
132175 try :
133176 value = int (value )
@@ -177,10 +220,20 @@ def sanitize_sameas(sameas):
177220 try :
178221 sanitized [key ] = int (value )
179222 except (TypeError , ValueError ):
180- pass
223+ print (
224+ f"Warning: discarding sameAs '{ key } ' value { value !r} : not a valid integer." ,
225+ file = sys .stderr ,
226+ )
181227 continue
182- if isinstance (value , str ) and re .match (patterns .get (key , r".+" ), value ):
228+ pattern = patterns .get (key , r".+" )
229+ if isinstance (value , str ) and re .match (pattern , value ):
183230 sanitized [key ] = value
231+ else :
232+ print (
233+ f"Warning: discarding sameAs '{ key } ' value { value !r} : does not match expected "
234+ f"pattern { pattern !r} ." ,
235+ file = sys .stderr ,
236+ )
184237 return sanitized
185238
186239
@@ -239,6 +292,19 @@ def main():
239292 chebi_id = sameas .get ("ChEBI" , "" ).replace ("CHEBI:" , "" )
240293 chebi_data = get_chebi (chebi_id ) if chebi_id else {}
241294
295+ # MetaboLights is not exposed by UniChem; derive it from the ChEBI id.
296+ if chebi_id and "metabolights" not in sameas :
297+ metabolights_id = get_metabolights (chebi_id )
298+ if metabolights_id :
299+ sameas ["metabolights" ] = metabolights_id
300+
301+ # CAS Registry Numbers are not exposed by UniChem; fetch them from CAS
302+ # Common Chemistry (requires the CAS_API_KEY environment variable).
303+ if "cas" not in sameas :
304+ cas_rn = get_cas (inchikey )
305+ if cas_rn and re .match (r"^\d{1,7}-\d{2}-\d$" , cas_rn ):
306+ sameas ["cas" ] = cas_rn
307+
242308 # Collect alternate names with priority
243309 alternate_names = []
244310
0 commit comments