Coverage for nexusLIMS/extractors/profiles.py: 100%
29 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
1"""Instrument profile system for customizing extraction behavior.
3This module provides a registry for instrument-specific extraction profiles,
4enabling easy customization of metadata extraction for different microscopes
5without modifying core extractor code.
7The profile system is the key extensibility mechanism for NexusLIMS - each
8installation has unique instruments, and profiles make it trivial to add
9instrument-specific behavior.
10"""
12from __future__ import annotations
14import logging
15from typing import TYPE_CHECKING
17if TYPE_CHECKING:
18 from nexusLIMS.db.models import Instrument
19 from nexusLIMS.extractors.base import InstrumentProfile
21_logger = logging.getLogger(__name__)
23__all__ = [
24 "InstrumentProfileRegistry",
25 "get_profile_registry",
26]
29class InstrumentProfileRegistry:
30 """
31 Registry for instrument-specific extraction profiles.
33 Manages registration and lookup of InstrumentProfile objects,
34 which customize extraction behavior for specific microscopes.
36 This is a singleton - use get_profile_registry() to access.
38 Examples
39 --------
40 Register a profile:
42 >>> from nexusLIMS.extractors.base import InstrumentProfile
43 >>> from nexusLIMS.extractors.profiles import get_profile_registry
44 >>>
45 >>> titan_profile = InstrumentProfile(
46 ... instrument_id="FEI-Titan-STEM-630901",
47 ... parsers={"microscope": parse_643_titan},
48 ... static_metadata={"nx_meta.Facility": "NIST"}
49 ... )
50 >>>
51 >>> registry = get_profile_registry()
52 >>> registry.register(titan_profile)
54 Retrieve a profile:
56 >>> from nexusLIMS.instruments import get_instr_from_filepath
57 >>> from pathlib import Path
58 >>>
59 >>> instrument = get_instr_from_filepath(Path("/path/to/file.dm3"))
60 >>> profile = registry.get_profile(instrument)
61 >>> if profile:
62 ... print(f"Using custom profile for {instrument.name}")
63 """
65 def __init__(self):
66 """Initialize the profile registry."""
67 self._profiles: dict[str, InstrumentProfile] = {}
68 _logger.debug("Initialized InstrumentProfileRegistry")
70 def register(self, profile: InstrumentProfile) -> None:
71 """
72 Register an instrument profile.
74 Parameters
75 ----------
76 profile
77 The profile to register
79 Raises
80 ------
81 ValueError
82 If a profile with the same instrument_id is already registered
84 Examples
85 --------
86 >>> from nexusLIMS.extractors.base import InstrumentProfile
87 >>> profile = InstrumentProfile(instrument_id="FEI-Quanta-12345")
88 >>> registry = get_profile_registry()
89 >>> registry.register(profile)
90 """
91 if profile.instrument_id in self._profiles:
92 _logger.warning(
93 "Replacing existing profile for instrument: %s",
94 profile.instrument_id,
95 )
97 self._profiles[profile.instrument_id] = profile
98 _logger.debug("Registered profile for: %s", profile.instrument_id)
100 def get_profile(self, instrument: Instrument | None) -> InstrumentProfile | None:
101 """
102 Get the profile for a specific instrument.
104 Parameters
105 ----------
106 instrument
107 The instrument to look up, or None
109 Returns
110 -------
111 InstrumentProfile | None
112 The profile for this instrument, or None if no profile registered
114 Examples
115 --------
116 >>> from nexusLIMS.instruments import get_instr_from_filepath
117 >>> from pathlib import Path
118 >>>
119 >>> instrument = get_instr_from_filepath(Path("/path/to/file.dm3"))
120 >>> registry = get_profile_registry()
121 >>> profile = registry.get_profile(instrument)
122 >>> if profile:
123 ... # Apply custom parsers
124 ... for name, parser_func in profile.parsers.items():
125 ... metadata = parser_func(metadata)
126 """
127 if instrument is None:
128 return None
130 instrument_id = instrument.name
131 return self._profiles.get(instrument_id)
133 def get_all_profiles(self) -> dict[str, InstrumentProfile]:
134 """
135 Get all registered profiles.
137 Returns
138 -------
139 dict[str, InstrumentProfile]
140 Dictionary mapping instrument IDs to profiles
142 Examples
143 --------
144 >>> registry = get_profile_registry()
145 >>> all_profiles = registry.get_all_profiles()
146 >>> for instr_id, profile in all_profiles.items():
147 ... print(f"{instr_id}: {len(profile.parsers)} custom parsers")
148 """
149 return self._profiles.copy()
151 def clear(self) -> None:
152 """
153 Clear all registered profiles.
155 Primarily used for testing.
157 Examples
158 --------
159 >>> registry = get_profile_registry()
160 >>> registry.clear()
161 """
162 self._profiles.clear()
163 _logger.debug("Cleared all instrument profiles")
166# Singleton instance
167_profile_registry: InstrumentProfileRegistry | None = None
170def get_profile_registry() -> InstrumentProfileRegistry:
171 """
172 Get the global instrument profile registry (singleton).
174 Returns
175 -------
176 InstrumentProfileRegistry
177 The global profile registry instance
179 Examples
180 --------
181 >>> from nexusLIMS.extractors.profiles import get_profile_registry
182 >>> registry = get_profile_registry()
183 >>> # Always returns the same instance
184 >>> assert get_profile_registry() is registry
185 """
186 global _profile_registry # noqa: PLW0603
187 if _profile_registry is None:
188 _profile_registry = InstrumentProfileRegistry()
189 return _profile_registry