#!/usr/bin/env python3
#
# nist_ms_search.py
"""
Configuration for :mod:`pyms_nist_search` and NIST MS Search.
"""
#
# Copyright © 2023 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
#
# stdlib
import io
from contextlib import contextmanager, redirect_stdout
from tempfile import TemporaryDirectory
from typing import Any, Dict, Iterator, List, Optional, Tuple
# 3rd party
import attrs
import pyms_nist_search
from dom_toml.config import Config
from dom_toml.config.fields import Boolean, String
from domdf_python_tools.typing import PathLike
from libgunshotmatch.utils import _fix_init_annotations
__all__ = (
"PyMSNISTSearchCfg",
"nist_ms_search_engine",
"LazyEngine",
"engine_on_demand",
"get_reference_data_for_cas",
)
[docs]@_fix_init_annotations
@attrs.define
class PyMSNISTSearchCfg(Config):
"""
Configuration for :mod:`pyms_nist_search`.
.. autosummary-widths:: 40/100
"""
#: Absolute path to the NIST library (mainlib or user).
library_path: str = String.field(default=attrs.NOTHING)
#: :py:obj:`True` for user libraries; :py:obj:`False` for the NIST mainlib.
user_library: bool = Boolean.field(default=False)
def _create_engine(config: PyMSNISTSearchCfg, workdir: PathLike, **kwargs) -> pyms_nist_search.Engine:
if config.user_library:
library_type = pyms_nist_search.NISTMS_USER_LIB
else:
library_type = pyms_nist_search.NISTMS_MAIN_LIB
return pyms_nist_search.Engine(config.library_path, library_type, workdir, **kwargs)
[docs]@contextmanager
def nist_ms_search_engine(config: PyMSNISTSearchCfg, **kwargs) -> Iterator[pyms_nist_search.Engine]:
r"""
Initialize the NIST MS Serch engine from :mod:`pyms_nist_search`.
:param config:
:param \*\*kwargs: Keyword arguments for :class:`pyms_nist_search.win_engine.Engine`
"""
with TemporaryDirectory() as workdir:
with _create_engine(config, workdir, **kwargs) as search:
# with pyms_nist_search.Engine(config.library_path, library_type, workdir, **kwargs) as search:
yield search
[docs]class LazyEngine:
r"""
Initialize the NIST MS Serch engine on demand.
:param config:
:param \*\*kwargs: Keyword arguments for :class:`pyms_nist_search.win_engine.Engine`
.. versionadded:: 0.2.0
"""
def __init__(self, config: PyMSNISTSearchCfg, **kwargs):
self.config: PyMSNISTSearchCfg = config
self.kwargs: Dict[str, Any] = kwargs
self._engine: Optional[pyms_nist_search.Engine] = None
self._workdir = TemporaryDirectory()
[docs] def deinit(self) -> None:
"""
Cleanup the underlying engine and temporary directory.
"""
if self._engine is not None:
self._engine.uninit()
self._workdir.cleanup()
@property
def engine(self) -> pyms_nist_search.Engine:
"""
The NIST MS Search engine.
The engine is created the first time this property is accessed.
"""
if self._engine is None:
self._engine = _create_engine(self.config, self._workdir.name, **self.kwargs)
return self._engine
[docs]@contextmanager
def engine_on_demand(config: PyMSNISTSearchCfg, **kwargs) -> Iterator[LazyEngine]:
r"""
Defer initialization of the NIST MS Serch engine until required (if at all).
:param config:
:param \*\*kwargs: Keyword arguments for :class:`pyms_nist_search.win_engine.Engine`
:rtype:
.. versionadded:: 0.2.0
"""
le = LazyEngine(config, **kwargs)
yield le
le.deinit()
[docs]def get_reference_data_for_cas(
cas: str,
pyms_nist_search_config: PyMSNISTSearchCfg,
) -> Tuple[str, Tuple[List[int], List[float]]]:
"""
Returns the name and mass spectra of the compound with the given CAS number.
:param cas:
:param pyms_nist_search_config:
:rtype:
.. versionadded:: 0.11.0
"""
debug_stdout = io.StringIO()
with redirect_stdout(debug_stdout):
with engine_on_demand(pyms_nist_search_config) as search:
cas_search_results = search.engine.cas_search(cas)
other_compound: pyms_nist_search.ReferenceData = search.engine.get_reference_data(
cas_search_results[0].spec_loc,
)
assert other_compound.mass_spec is not None
return other_compound.name, (other_compound.mass_spec.mass_list, other_compound.mass_spec.intensity_list)