msmetaenhancer 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msmetaenhancer-0.4.0/LICENSE +21 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/__init__.py +3 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/app.py +103 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Annotator.py +91 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Converter.py +81 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Curator.py +75 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/__init__.py +0 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/__init__.py +0 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/ComputeConverter.py +9 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/RDKit.py +70 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/__init__.py +3 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/BridgeDb.py +135 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/CIR.py +111 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/CTS.py +129 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/IDSM.py +162 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/PubChem.py +232 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/WebConverter.py +122 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/__init__.py +7 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/Data.py +24 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/DataFrame.py +49 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/Spectra.py +66 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/__init__.py +4 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/ConverterBuilder.py +39 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Errors.py +37 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Generic.py +11 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Job.py +40 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/LogRecord.py +35 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Logger.py +92 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Metrics.py +48 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Monitor.py +61 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Throttler.py +54 -0
- msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/__init__.py +3 -0
- msmetaenhancer-0.4.0/PKG-INFO +105 -0
- msmetaenhancer-0.4.0/README.md +75 -0
- msmetaenhancer-0.4.0/pyproject.toml +48 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 RECETOX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from MSMetaEnhancer.libs.Annotator import Annotator
|
|
6
|
+
from MSMetaEnhancer.libs.Converter import Converter
|
|
7
|
+
from MSMetaEnhancer.libs.Curator import Curator
|
|
8
|
+
from MSMetaEnhancer.libs.data import Spectra, DataFrame
|
|
9
|
+
from MSMetaEnhancer.libs.utils import logger
|
|
10
|
+
from MSMetaEnhancer.libs.utils.ConverterBuilder import ConverterBuilder
|
|
11
|
+
from MSMetaEnhancer.libs.utils.Errors import UnknownSpectraFormat
|
|
12
|
+
from MSMetaEnhancer.libs.utils.Job import convert_to_jobs
|
|
13
|
+
from MSMetaEnhancer.libs.utils.Monitor import Monitor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Application:
|
|
17
|
+
def __init__(self, log_level='info', log_file=None):
|
|
18
|
+
self.data = None
|
|
19
|
+
logger.setup(log_level, log_file)
|
|
20
|
+
|
|
21
|
+
def load_data(self, filename, file_format):
|
|
22
|
+
"""
|
|
23
|
+
High level method to load Spectra data from given file.
|
|
24
|
+
|
|
25
|
+
:param filename: path to source spectra file
|
|
26
|
+
:param file_format: format of spectra
|
|
27
|
+
"""
|
|
28
|
+
if file_format in ['msp', 'mgf', 'json']:
|
|
29
|
+
self.data = Spectra()
|
|
30
|
+
elif file_format in ['csv', 'tsv', 'xlsx']:
|
|
31
|
+
self.data = DataFrame()
|
|
32
|
+
else:
|
|
33
|
+
raise UnknownSpectraFormat(f'Format {file_format} not supported.')
|
|
34
|
+
self.data.load_data(filename, file_format)
|
|
35
|
+
|
|
36
|
+
def save_data(self, filename, file_format):
|
|
37
|
+
"""
|
|
38
|
+
High level method to save Spectra data to given file.
|
|
39
|
+
|
|
40
|
+
:param filename: path to target file
|
|
41
|
+
:param file_format: desired format of spectra
|
|
42
|
+
"""
|
|
43
|
+
self.data.save_data(filename, file_format)
|
|
44
|
+
|
|
45
|
+
def curate_metadata(self):
|
|
46
|
+
"""
|
|
47
|
+
Updates metadata by curation process.
|
|
48
|
+
|
|
49
|
+
This includes e.g. normalisation of CAS numbers.
|
|
50
|
+
"""
|
|
51
|
+
curated_metadata = Curator().curate_metadata(self.data.get_metadata())
|
|
52
|
+
self.data.fuse_metadata(curated_metadata)
|
|
53
|
+
|
|
54
|
+
async def annotate_spectra(self,
|
|
55
|
+
converters,
|
|
56
|
+
jobs=None,
|
|
57
|
+
repeat: bool = False,
|
|
58
|
+
monitor: Monitor = Monitor(),
|
|
59
|
+
annotator: Annotator = Annotator()):
|
|
60
|
+
"""
|
|
61
|
+
Annotates current Spectra data by specified jobs.
|
|
62
|
+
|
|
63
|
+
Used converters must be specified.
|
|
64
|
+
Jobs do not have to be given, all available jobs will be executed instead.
|
|
65
|
+
|
|
66
|
+
:param converters: given list of converters names
|
|
67
|
+
:param jobs: list specifying jobs to be executed
|
|
68
|
+
:param repeat: if some metadata was added, all jobs are executed again
|
|
69
|
+
:param monitor: given Monitor object to observe status of services
|
|
70
|
+
:param annotator: given Annotator object to run the actual annotation
|
|
71
|
+
"""
|
|
72
|
+
async with aiohttp.ClientSession() as session:
|
|
73
|
+
builder = ConverterBuilder()
|
|
74
|
+
builder.validate_converters(converters)
|
|
75
|
+
converters, web_converters = builder.build_converters(session, converters)
|
|
76
|
+
|
|
77
|
+
annotator.set_converters(converters)
|
|
78
|
+
monitor.set_converters(web_converters)
|
|
79
|
+
|
|
80
|
+
# start converters status checker and wait for first status
|
|
81
|
+
try:
|
|
82
|
+
monitor.start()
|
|
83
|
+
monitor.first_check.wait()
|
|
84
|
+
|
|
85
|
+
# create all possible jobs if not given
|
|
86
|
+
if not jobs:
|
|
87
|
+
jobs = []
|
|
88
|
+
converter: Converter
|
|
89
|
+
for converter in converters.values():
|
|
90
|
+
jobs += converter.get_conversion_functions()
|
|
91
|
+
jobs = convert_to_jobs(jobs)
|
|
92
|
+
|
|
93
|
+
metadata_list = self.data.get_metadata()
|
|
94
|
+
|
|
95
|
+
logger.set_target_attributes(jobs, len(metadata_list))
|
|
96
|
+
|
|
97
|
+
results = await asyncio.gather(*[annotator.annotate(metadata, jobs, repeat)
|
|
98
|
+
for metadata in metadata_list])
|
|
99
|
+
finally:
|
|
100
|
+
monitor.join()
|
|
101
|
+
|
|
102
|
+
self.data.fuse_metadata(results)
|
|
103
|
+
logger.write_metrics()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
from MSMetaEnhancer.libs.Curator import Curator
|
|
4
|
+
from MSMetaEnhancer.libs.utils import logger
|
|
5
|
+
from MSMetaEnhancer.libs.utils.Errors import TargetAttributeNotRetrieved, SourceAttributeNotAvailable, \
|
|
6
|
+
ServiceNotAvailable, UnknownResponse, DataAlreadyPresent
|
|
7
|
+
from MSMetaEnhancer.libs.utils.Logger import LogRecord
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Annotator:
|
|
11
|
+
"""
|
|
12
|
+
Annotator is responsible for annotation process of single spectra.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.converters = dict()
|
|
16
|
+
self.curator = Curator()
|
|
17
|
+
|
|
18
|
+
def set_converters(self, converters):
|
|
19
|
+
self.converters = converters
|
|
20
|
+
|
|
21
|
+
async def annotate(self, metadata, jobs, repeat=False):
|
|
22
|
+
"""
|
|
23
|
+
Runs all jobs to add annotations to given dictionary containing metadata
|
|
24
|
+
|
|
25
|
+
The method goes through specified jobs of form (Source, Target, Converter)
|
|
26
|
+
and tries to obtain 'Target' attribute based on 'Source' attribute using
|
|
27
|
+
'Converter' converter.
|
|
28
|
+
|
|
29
|
+
:param metadata: given metadata
|
|
30
|
+
:param jobs: specified list of jobs to be executed
|
|
31
|
+
:param repeat: if some metadata was added, all jobs are executed again
|
|
32
|
+
:return: annotated dictionary
|
|
33
|
+
"""
|
|
34
|
+
cache = dict()
|
|
35
|
+
log = LogRecord(dict(metadata))
|
|
36
|
+
logger.add_coverage_before(metadata.keys())
|
|
37
|
+
|
|
38
|
+
added_metadata = True
|
|
39
|
+
while added_metadata:
|
|
40
|
+
added_metadata = False
|
|
41
|
+
for job in jobs:
|
|
42
|
+
if job.target not in metadata:
|
|
43
|
+
try:
|
|
44
|
+
metadata, cache = await self.execute_job_with_cache(job, metadata, cache, log)
|
|
45
|
+
if repeat:
|
|
46
|
+
added_metadata = True
|
|
47
|
+
except (SourceAttributeNotAvailable, TargetAttributeNotRetrieved) as exc:
|
|
48
|
+
log.update(exc, job, level=3)
|
|
49
|
+
except (ServiceNotAvailable, UnknownResponse) as exc:
|
|
50
|
+
log.update(exc, job, level=2)
|
|
51
|
+
except Exception:
|
|
52
|
+
log.update(Exception(traceback.format_exc()), job, level=1)
|
|
53
|
+
else:
|
|
54
|
+
log.update(DataAlreadyPresent(f'Requested attribute {job.target} already present.'), job, level=2)
|
|
55
|
+
|
|
56
|
+
logger.add_logs(log)
|
|
57
|
+
logger.add_coverage_after(metadata.keys())
|
|
58
|
+
|
|
59
|
+
return metadata
|
|
60
|
+
|
|
61
|
+
async def execute_job_with_cache(self, job, metadata, cache, warning):
|
|
62
|
+
"""
|
|
63
|
+
Execute given job in cached mode. Cache is converter specific
|
|
64
|
+
and spectra specific.
|
|
65
|
+
|
|
66
|
+
Raises TargetAttributeNotRetrieved
|
|
67
|
+
|
|
68
|
+
:param job: given job to be executed
|
|
69
|
+
:param metadata: data to be annotated by the job
|
|
70
|
+
:param cache: given cache for this spectra
|
|
71
|
+
:param warning: object storing warnings related to current metadata
|
|
72
|
+
:return: updated metadata and cache
|
|
73
|
+
"""
|
|
74
|
+
# make sure the job makes sense
|
|
75
|
+
converter, data = job.validate(self.converters, metadata)
|
|
76
|
+
|
|
77
|
+
cache[job.converter] = cache.get(job.converter, dict())
|
|
78
|
+
if job.target in cache[job.converter]:
|
|
79
|
+
metadata[job.target] = cache[job.converter][job.target]
|
|
80
|
+
else:
|
|
81
|
+
if converter.is_available:
|
|
82
|
+
result = await converter.convert(job.source, job.target, data)
|
|
83
|
+
result = self.curator.filter_invalid_metadata(result, warning, job)
|
|
84
|
+
cache[job.converter].update(result)
|
|
85
|
+
if job.target in cache[job.converter]:
|
|
86
|
+
metadata[job.target] = cache[job.converter][job.target]
|
|
87
|
+
else:
|
|
88
|
+
raise TargetAttributeNotRetrieved('No data retrieved.')
|
|
89
|
+
else:
|
|
90
|
+
raise ServiceNotAvailable(f'Service {job.converter} not available.')
|
|
91
|
+
return metadata, cache
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Converter(ABC):
|
|
5
|
+
"""
|
|
6
|
+
General class for conversions.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.is_available = True
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def converter_name(self):
|
|
13
|
+
return self.__class__.__name__
|
|
14
|
+
|
|
15
|
+
def __hash__(self):
|
|
16
|
+
return hash(self.converter_name)
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def convert(self, source, target, data):
|
|
20
|
+
"""
|
|
21
|
+
Converts specified {source} attribute (provided in {data}) to {target} attribute.
|
|
22
|
+
|
|
23
|
+
:param source: given attribute name
|
|
24
|
+
:param target: required attribute name
|
|
25
|
+
:param data: given attribute value
|
|
26
|
+
:return: obtained value of target attribute
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def create_top_level_conversion_methods(self, conversions, asynch=True):
|
|
31
|
+
"""
|
|
32
|
+
Method to create and set dynamic methods defined in conversions
|
|
33
|
+
|
|
34
|
+
:param conversions: triples of form (from, to, method)
|
|
35
|
+
:param asynch: whether to create asynchronous methods
|
|
36
|
+
"""
|
|
37
|
+
for conversion in conversions:
|
|
38
|
+
create_top_level_method(self, *conversion, asynch)
|
|
39
|
+
|
|
40
|
+
def get_conversion_functions(self) -> list:
|
|
41
|
+
"""
|
|
42
|
+
Method to compute all available conversion functions.
|
|
43
|
+
|
|
44
|
+
Assumes that the functions always have from {source}_to_{target}
|
|
45
|
+
|
|
46
|
+
:return: a list of available conversion functions
|
|
47
|
+
"""
|
|
48
|
+
available_conversions = []
|
|
49
|
+
methods = [method_name for method_name in dir(self) if '_to_' in method_name]
|
|
50
|
+
for method in methods:
|
|
51
|
+
available_conversions.append((*method.split('_to_'), self.converter_name))
|
|
52
|
+
return available_conversions
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create_top_level_method(obj: Converter, source: str, target: str, method: str, asynch: bool = True):
|
|
56
|
+
"""
|
|
57
|
+
Assign a new method to {obj} called {source}_to_{target} which calls {method}.
|
|
58
|
+
|
|
59
|
+
:param obj: given object (typically a Converter)
|
|
60
|
+
:param source: attribute name used as source of data
|
|
61
|
+
:param target: attribute name obtained using this dynamic method
|
|
62
|
+
:param method: method which is called in the object with single argument
|
|
63
|
+
:param asynch: whether to create asynchronous methods
|
|
64
|
+
"""
|
|
65
|
+
async def async_conversion(key):
|
|
66
|
+
return await getattr(obj, str(method))(key)
|
|
67
|
+
|
|
68
|
+
def sync_conversion(key):
|
|
69
|
+
return getattr(obj, str(method))(key)
|
|
70
|
+
|
|
71
|
+
doc = f'Convert {source} to {target} using {obj.__class__.__name__} converter'
|
|
72
|
+
name = f'{source}_to_{target}'
|
|
73
|
+
|
|
74
|
+
if asynch:
|
|
75
|
+
async_conversion.__doc__ = doc
|
|
76
|
+
async_conversion.__name__ = name
|
|
77
|
+
setattr(obj, async_conversion.__name__, async_conversion)
|
|
78
|
+
else:
|
|
79
|
+
sync_conversion.__doc__ = doc
|
|
80
|
+
sync_conversion.__name__ = name
|
|
81
|
+
setattr(obj, sync_conversion.__name__, sync_conversion)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from matchms import metadata_utils
|
|
2
|
+
from MSMetaEnhancer.libs.utils.Errors import InvalidAttributeFormat
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Curator:
|
|
6
|
+
"""
|
|
7
|
+
Curator makes sure that all data is curated before the actual annotation can proceed.
|
|
8
|
+
Currently, fixing CAS numbers to correct format is supported.
|
|
9
|
+
|
|
10
|
+
Additionally, it supports metadata validation to make sure the produced data are correct.
|
|
11
|
+
"""
|
|
12
|
+
def curate_metadata(self, metadata_list):
|
|
13
|
+
"""
|
|
14
|
+
Iterates over given metadata and curates individual entries.
|
|
15
|
+
|
|
16
|
+
:param metadata_list: given metadata
|
|
17
|
+
:return: curated metadata
|
|
18
|
+
"""
|
|
19
|
+
curated_metadata_list = []
|
|
20
|
+
for metadata in metadata_list:
|
|
21
|
+
curated_metadata_list.append(self.curate_casno(metadata))
|
|
22
|
+
return curated_metadata_list
|
|
23
|
+
|
|
24
|
+
def curate_casno(self, metadata):
|
|
25
|
+
"""
|
|
26
|
+
Curate metadata of particular spectra.
|
|
27
|
+
|
|
28
|
+
:param metadata: given metadata
|
|
29
|
+
:return: curated metadata
|
|
30
|
+
"""
|
|
31
|
+
if 'casno' in metadata:
|
|
32
|
+
metadata['casno'] = self.fix_cas_number(metadata['casno'])
|
|
33
|
+
return metadata
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def fix_cas_number(cas_number):
|
|
37
|
+
"""
|
|
38
|
+
Adds dashes to CAS number.
|
|
39
|
+
|
|
40
|
+
:param cas_number: given CAS number
|
|
41
|
+
:return: CAS number enriched by dashes (if needed)
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(cas_number, str):
|
|
44
|
+
if "-" not in cas_number:
|
|
45
|
+
return f'{cas_number[:-3]}-{cas_number[-3:-1]}-{cas_number[-1]}'
|
|
46
|
+
return cas_number
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def filter_invalid_metadata(metadata, log, job):
|
|
50
|
+
"""
|
|
51
|
+
Validates metadata and filters out invalid ones.
|
|
52
|
+
|
|
53
|
+
:param metadata: metadata content
|
|
54
|
+
:param log: object storing logs related to current metadata
|
|
55
|
+
:param job: executed job
|
|
56
|
+
:return: only valid metadata
|
|
57
|
+
"""
|
|
58
|
+
filters = {
|
|
59
|
+
'smiles': metadata_utils.is_valid_smiles,
|
|
60
|
+
'canonical_smiles': metadata_utils.is_valid_smiles,
|
|
61
|
+
'isomeric_smiles': metadata_utils.is_valid_smiles,
|
|
62
|
+
'inchi': metadata_utils.is_valid_inchi,
|
|
63
|
+
'inchikey': metadata_utils.is_valid_inchikey
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
valid_metadata = {}
|
|
67
|
+
for (attribute, value) in metadata.items():
|
|
68
|
+
if attribute in filters.keys():
|
|
69
|
+
if filters[attribute](value):
|
|
70
|
+
valid_metadata[attribute] = value
|
|
71
|
+
else:
|
|
72
|
+
log.update(InvalidAttributeFormat(f'Obtained {attribute} in invalid format: {value}'), job, level=2)
|
|
73
|
+
else:
|
|
74
|
+
valid_metadata[attribute] = value
|
|
75
|
+
return valid_metadata
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from rdkit.Chem.Descriptors import ExactMolWt
|
|
3
|
+
from rdkit.Chem import MolFromSmiles, MolToSmiles
|
|
4
|
+
from rdkit.Chem.inchi import MolFromInchi
|
|
5
|
+
from rdkit.Chem import Atom
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from MSMetaEnhancer.libs.converters.compute.ComputeConverter import ComputeConverter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RDKit(ComputeConverter):
|
|
12
|
+
"""
|
|
13
|
+
RDKit is a collection of chemo-informatics and machine-learning software.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__()
|
|
17
|
+
# generate top level methods defining allowed conversions
|
|
18
|
+
conversions = [('smiles', 'mw', 'from_smiles'),
|
|
19
|
+
('canonical_smiles', 'mw', 'from_smiles'),
|
|
20
|
+
('isomeric_smiles', 'mw', 'from_smiles')]
|
|
21
|
+
self.create_top_level_conversion_methods(conversions, asynch=False)
|
|
22
|
+
|
|
23
|
+
def from_smiles(self, smiles):
|
|
24
|
+
"""
|
|
25
|
+
Compute molecular exact weight from SMILES.
|
|
26
|
+
|
|
27
|
+
:param smiles: given SMILES
|
|
28
|
+
:return: computed molecular weight
|
|
29
|
+
"""
|
|
30
|
+
weight = ExactMolWt(MolFromSmiles(smiles))
|
|
31
|
+
return {'mw': weight}
|
|
32
|
+
|
|
33
|
+
def inchi_to_canonical_smiles(self, inchi):
|
|
34
|
+
"""
|
|
35
|
+
Compute canonical SMILES from InChI.
|
|
36
|
+
|
|
37
|
+
:param inchi: given InChI
|
|
38
|
+
:return: computed canonical SMILES
|
|
39
|
+
"""
|
|
40
|
+
smiles = MolToSmiles(MolFromInchi(inchi), isomericSmiles=False)
|
|
41
|
+
return {'canonical_smiles': smiles}
|
|
42
|
+
|
|
43
|
+
def inchi_to_isomeric_smiles(self, inchi):
|
|
44
|
+
"""
|
|
45
|
+
Compute isomeric SMILES from InChI.
|
|
46
|
+
|
|
47
|
+
:param inchi: given InChI
|
|
48
|
+
:return: computed isomeric SMILES
|
|
49
|
+
"""
|
|
50
|
+
smiles = MolToSmiles(MolFromInchi(inchi))
|
|
51
|
+
return {'isomeric_smiles': smiles}
|
|
52
|
+
|
|
53
|
+
def formula_to_mw(self, formula):
|
|
54
|
+
"""
|
|
55
|
+
Compute molecular exact weight from molecular formula.
|
|
56
|
+
|
|
57
|
+
:param formula: given formula
|
|
58
|
+
:return: computed molecular weight
|
|
59
|
+
"""
|
|
60
|
+
parts = re.findall("[A-Z][a-z]?|[0-9]+", formula)
|
|
61
|
+
mass = 0
|
|
62
|
+
|
|
63
|
+
for index in range(len(parts)):
|
|
64
|
+
if parts[index].isnumeric():
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
atom = Atom(parts[index])
|
|
68
|
+
multiplier = int(parts[index + 1]) if len(parts) > index + 1 and parts[index + 1].isnumeric() else 1
|
|
69
|
+
mass += atom.GetMass() * multiplier
|
|
70
|
+
return {'mw': mass}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from MSMetaEnhancer.libs.converters.web.WebConverter import WebConverter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BridgeDb(WebConverter):
|
|
5
|
+
"""
|
|
6
|
+
BridgeDb is a framework to map identifiers between various biological databases. These mappings are provided for
|
|
7
|
+
genes, proteins, genetic variants, metabolites, and metabolic reactions
|
|
8
|
+
|
|
9
|
+
More info about the available conversions: https://bridgedb.github.io/
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self, session):
|
|
12
|
+
super().__init__(session)
|
|
13
|
+
# service URLs
|
|
14
|
+
self.endpoints = {'BridgeDb': 'https://webservice.bridgedb.org/Human/xrefs/'}
|
|
15
|
+
|
|
16
|
+
self.codes = {'hmdbid': 'Ch', 'pubchemid': 'Cpc', 'chemspiderid': 'Cs', 'wikidataid': 'Wd', 'chebiid': 'Ce',
|
|
17
|
+
'keggid': 'Ck'}
|
|
18
|
+
self.identifiers = {'PubChem-compound': 'pubchemid', 'Chemspider': 'chemspiderid', 'ChEBI': 'chebiid',
|
|
19
|
+
'HMDB': 'hmdbid', 'Wikidata': 'wikidataid', 'KEGG Compound': 'keggid'}
|
|
20
|
+
|
|
21
|
+
# generate top level methods defining allowed conversions
|
|
22
|
+
conversions = [('hmdbid', 'pubchemid', 'from_hmdbid'),
|
|
23
|
+
('hmdbid', 'chemspiderid', 'from_hmdbid'),
|
|
24
|
+
('hmdbid', 'wikidataid', 'from_hmdbid'),
|
|
25
|
+
('hmdbid', 'chebiid', 'from_hmdbid'),
|
|
26
|
+
('hmdbid', 'keggid', 'from_hmdbid'),
|
|
27
|
+
('pubchemid', 'hmdbid', 'from_pubchemid'),
|
|
28
|
+
('pubchemid', 'chemspiderid', 'from_pubchemid'),
|
|
29
|
+
('pubchemid', 'wikidataid', 'from_pubchemid'),
|
|
30
|
+
('pubchemid', 'chebiid', 'from_pubchemid'),
|
|
31
|
+
('pubchemid', 'keggid', 'from_pubchemid'),
|
|
32
|
+
('chemspiderid', 'hmdbid', 'from_chemspiderid'),
|
|
33
|
+
('chemspiderid', 'pubchemid', 'from_chemspiderid'),
|
|
34
|
+
('chemspiderid', 'wikidataid', 'from_chemspiderid'),
|
|
35
|
+
('chemspiderid', 'chebiid', 'from_chemspiderid'),
|
|
36
|
+
('chemspiderid', 'keggid', 'from_chemspiderid'),
|
|
37
|
+
('wikidataid', 'hmdbid', 'from_wikidataid'),
|
|
38
|
+
('wikidataid', 'pubchemid', 'from_wikidataid'),
|
|
39
|
+
('wikidataid', 'chemspiderid', 'from_wikidataid'),
|
|
40
|
+
('wikidataid', 'chebiid', 'from_wikidataid'),
|
|
41
|
+
('wikidataid', 'keggid', 'from_wikidataid'),
|
|
42
|
+
('chebiid', 'hmdbid', 'from_chebiid'),
|
|
43
|
+
('chebiid', 'pubchemid', 'from_chebiid'),
|
|
44
|
+
('chebiid', 'chemspiderid', 'from_chebiid'),
|
|
45
|
+
('chebiid', 'wikidataid', 'from_chebiid'),
|
|
46
|
+
('chebiid', 'keggid', 'from_chebiid'),
|
|
47
|
+
('keggid', 'hmdbid', 'from_keggid'),
|
|
48
|
+
('keggid', 'pubchemid', 'from_keggid'),
|
|
49
|
+
('keggid', 'chemspiderid', 'from_keggid'),
|
|
50
|
+
('keggid', 'wikidataid', 'from_keggid'),
|
|
51
|
+
('keggid', 'chebiid', 'from_keggid'),
|
|
52
|
+
]
|
|
53
|
+
self.create_top_level_conversion_methods(conversions)
|
|
54
|
+
|
|
55
|
+
async def from_hmdbid(self, hmdbid):
|
|
56
|
+
"""
|
|
57
|
+
Convert HMDB ID to all possible IDs using BridgeDb web service
|
|
58
|
+
|
|
59
|
+
:param hmdbid: given HMDB ID number
|
|
60
|
+
:return: obtained IDs
|
|
61
|
+
"""
|
|
62
|
+
args = f'{self.codes["hmdbid"]}/{hmdbid}'
|
|
63
|
+
return await self.call_service(args)
|
|
64
|
+
|
|
65
|
+
async def from_pubchemid(self, pubchemid):
|
|
66
|
+
"""
|
|
67
|
+
Convert PubChem ID to all possible IDs using BridgeDb web service
|
|
68
|
+
|
|
69
|
+
:param pubchemid: given PubChem ID number
|
|
70
|
+
:return: obtained IDs
|
|
71
|
+
"""
|
|
72
|
+
args = f'{self.codes["pubchemid"]}/{pubchemid}'
|
|
73
|
+
return await self.call_service(args)
|
|
74
|
+
|
|
75
|
+
async def from_chemspiderid(self, chemspiderid):
|
|
76
|
+
"""
|
|
77
|
+
Convert ChemSpider ID to all possible IDs using BridgeDb web service
|
|
78
|
+
|
|
79
|
+
:param chemspiderid: given ChemSpider ID number
|
|
80
|
+
:return: obtained IDs
|
|
81
|
+
"""
|
|
82
|
+
args = f'{self.codes["chemspiderid"]}/{chemspiderid}'
|
|
83
|
+
return await self.call_service(args)
|
|
84
|
+
|
|
85
|
+
async def from_wikidataid(self, wikidataid):
|
|
86
|
+
"""
|
|
87
|
+
Convert WikiData ID to all possible IDs using BridgeDb web service
|
|
88
|
+
|
|
89
|
+
:param wikidataid: given WikiData ID number
|
|
90
|
+
:return: obtained IDs
|
|
91
|
+
"""
|
|
92
|
+
args = f'{self.codes["wikidataid"]}/{wikidataid}'
|
|
93
|
+
return await self.call_service(args)
|
|
94
|
+
|
|
95
|
+
async def from_chebiid(self, chebiid):
|
|
96
|
+
"""
|
|
97
|
+
Convert ChEBI ID to all possible IDs using BridgeDb web service
|
|
98
|
+
|
|
99
|
+
:param chebiid: given ChEBI ID number
|
|
100
|
+
:return: obtained IDs
|
|
101
|
+
"""
|
|
102
|
+
args = f'{self.codes["chebiid"]}/{chebiid}'
|
|
103
|
+
return await self.call_service(args)
|
|
104
|
+
|
|
105
|
+
async def from_keggid(self, keggid):
|
|
106
|
+
"""
|
|
107
|
+
Convert KEGG ID to all possible IDs using BridgeDb web service
|
|
108
|
+
|
|
109
|
+
:param keggid: given KEGG ID number
|
|
110
|
+
:return: obtained IDs
|
|
111
|
+
"""
|
|
112
|
+
args = f'{self.codes["keggid"]}/{keggid}'
|
|
113
|
+
return await self.call_service(args)
|
|
114
|
+
|
|
115
|
+
async def call_service(self, args):
|
|
116
|
+
response = await self.query_the_service('BridgeDb', args)
|
|
117
|
+
if response:
|
|
118
|
+
return self.parse_attributes(response)
|
|
119
|
+
|
|
120
|
+
def parse_attributes(self, response):
|
|
121
|
+
"""
|
|
122
|
+
Parse all available attributes obtained using BridgeDb.
|
|
123
|
+
|
|
124
|
+
:param response: BridgeDb response to given ID
|
|
125
|
+
:return: all parsed data
|
|
126
|
+
"""
|
|
127
|
+
result = dict()
|
|
128
|
+
|
|
129
|
+
lines = response.split('\n')
|
|
130
|
+
for line in lines:
|
|
131
|
+
if line:
|
|
132
|
+
value, identifier = line.split('\t')
|
|
133
|
+
if identifier in self.identifiers.keys():
|
|
134
|
+
result[self.identifiers[identifier]] = value
|
|
135
|
+
return result
|