Source code for nexusLIMS.instruments
# pylint: disable=duplicate-code
"""
Methods and representations for instruments in a NexusLIMS system.
Attributes
----------
instrument_db : dict
A dictionary of :py:class:`~nexusLIMS.db.models.Instrument` objects.
Each object in this dictionary represents an instrument detected in the
NexusLIMS remote database.
"""
import logging
from pathlib import Path
from pydantic import ValidationError
from sqlmodel import Session as DBSession
from sqlmodel import create_engine, select
from nexusLIMS.config import settings
from nexusLIMS.db.engine import get_engine
from nexusLIMS.db.models import Instrument
from nexusLIMS.utils.paths import is_subpath
logging.basicConfig()
_logger = logging.getLogger(__name__)
# Lazy-loaded instrument cache. Populated on first access via
# _ensure_instrument_db_loaded(). Reset between tests by
# SingletonResetter.reset_instrument_cache().
instrument_db: dict = {}
_instrument_db_initialized = False
def _ensure_instrument_db_loaded():
"""Populate ``instrument_db`` from the database on first call."""
global _instrument_db_initialized # noqa: PLW0603
if not _instrument_db_initialized:
_instrument_db_initialized = True
instrument_db.update(_get_instrument_db())
def _get_instrument_db(db_path: Path | str | None = None):
"""
Get dictionary of instruments from the NexusLIMS database.
Parameters
----------
db_path : Path | str | None, optional
Path to the database file. If None, uses the path from settings.
This parameter is primarily for testing purposes.
Returns
-------
instrument_db : dict
A dictionary of `Instrument` instances that describe all the
instruments that were found in the ``instruments`` table of the
NexusLIMS database
"""
try:
# Use provided path or fall back to settings
_db_path = db_path if db_path is not None else settings.NX_DB_PATH
# Create temporary engine if non-default path (for testing)
if db_path is not None:
temp_engine = create_engine(f"sqlite:///{_db_path}")
else:
temp_engine = get_engine()
with DBSession(temp_engine) as session:
instruments_list = session.exec(select(Instrument)).all()
return {inst.instrument_pid: inst for inst in instruments_list}
except ValidationError as e:
_logger.debug(
"NexusLIMS config not available (standalone mode); "
"instrument database not loaded. Details: %s",
e,
)
return {}
except Exception as e:
_logger.warning(
"Could not connect to database or retrieve instruments. "
"Returning empty instrument dictionary.\n\n Details:\n %s",
e,
)
return {}
[docs]
def get_instr_from_filepath(path: Path) -> Instrument | None:
"""
Get an instrument object by a given path Using the NexusLIMS database.
Parameters
----------
path
A path (relative or absolute) to a file saved in the central
filestore that will be used to search for a matching instrument
Returns
-------
instrument : Instrument or None
An `Instrument` instance matching the path, or None if no match was
found
Examples
--------
>>> inst = get_instr_from_filepath('/path/to/file.dm3')
>>> str(inst)
'FEI-Titan-TEM-012345 in Bldg 1/Room A'
"""
_ensure_instrument_db_loaded()
for _, v in instrument_db.items():
if is_subpath(
path,
Path(settings.NX_INSTRUMENT_DATA_PATH) / v.filestore_path,
):
return v
return None
[docs]
def get_instr_from_calendar_name(cal_name):
"""
Get an instrument object from the NexusLIMS database by its calendar name.
Parameters
----------
cal_name : str
A calendar name (e.g. "FEITitanTEMEvents") that will be used to search
for a matching instrument in the ``api_url`` values
Returns
-------
instrument : Instrument or None
An `Instrument` instance matching the path, or None if no match was
found
Examples
--------
>>> inst = get_instr_from_calendar_name('FEITitanTEMEvents')
>>> str(inst)
'FEI-Titan-TEM-012345 in Bldg 1/Room A'
"""
_ensure_instrument_db_loaded()
for _, v in instrument_db.items():
if cal_name in v.api_url:
return v
return None
[docs]
def get_instr_from_api_url(api_url: str) -> Instrument | None:
"""
Get an instrument object from the NexusLIMS database by its ``api_url``.
Parameters
----------
api_url
An api_url (e.g. "FEITitanTEMEvents") that will be used to search
for a matching instrument in the ``api_url`` values
Returns
-------
Instrument
An ``Instrument`` instance matching the ``api_url``, or ``None`` if no
match was found
Examples
--------
>>> inst = get_instr_from_api_url('https://nemo.example.com/api/tools/?id=1')
>>> str(inst)
'FEI-Titan-STEM-012345 in Bldg 1/Room A'
"""
_ensure_instrument_db_loaded()
for _, v in instrument_db.items():
if api_url == v.api_url:
return v
return None