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.
Files changed (35) hide show
  1. msmetaenhancer-0.4.0/LICENSE +21 -0
  2. msmetaenhancer-0.4.0/MSMetaEnhancer/__init__.py +3 -0
  3. msmetaenhancer-0.4.0/MSMetaEnhancer/app.py +103 -0
  4. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Annotator.py +91 -0
  5. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Converter.py +81 -0
  6. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/Curator.py +75 -0
  7. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/__init__.py +0 -0
  8. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/__init__.py +0 -0
  9. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/ComputeConverter.py +9 -0
  10. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/RDKit.py +70 -0
  11. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/compute/__init__.py +3 -0
  12. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/BridgeDb.py +135 -0
  13. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/CIR.py +111 -0
  14. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/CTS.py +129 -0
  15. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/IDSM.py +162 -0
  16. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/PubChem.py +232 -0
  17. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/WebConverter.py +122 -0
  18. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/converters/web/__init__.py +7 -0
  19. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/Data.py +24 -0
  20. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/DataFrame.py +49 -0
  21. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/Spectra.py +66 -0
  22. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/data/__init__.py +4 -0
  23. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/ConverterBuilder.py +39 -0
  24. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Errors.py +37 -0
  25. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Generic.py +11 -0
  26. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Job.py +40 -0
  27. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/LogRecord.py +35 -0
  28. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Logger.py +92 -0
  29. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Metrics.py +48 -0
  30. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Monitor.py +61 -0
  31. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/Throttler.py +54 -0
  32. msmetaenhancer-0.4.0/MSMetaEnhancer/libs/utils/__init__.py +3 -0
  33. msmetaenhancer-0.4.0/PKG-INFO +105 -0
  34. msmetaenhancer-0.4.0/README.md +75 -0
  35. 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,3 @@
1
+ from MSMetaEnhancer.app import Application
2
+
3
+ __all__ = ["Application"]
@@ -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
@@ -0,0 +1,9 @@
1
+ from MSMetaEnhancer.libs.Converter import Converter
2
+
3
+
4
+ class ComputeConverter(Converter):
5
+ """
6
+ General class for computation conversion.
7
+ """
8
+ async def convert(self, source, target, data):
9
+ return getattr(self, f'{source}_to_{target}')(data)
@@ -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,3 @@
1
+ from MSMetaEnhancer.libs.converters.compute.RDKit import RDKit
2
+
3
+ __all__ = ['RDKit']
@@ -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