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

1"""Instrument profile system for customizing extraction behavior. 

2 

3This module provides a registry for instrument-specific extraction profiles, 

4enabling easy customization of metadata extraction for different microscopes 

5without modifying core extractor code. 

6 

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""" 

11 

12from __future__ import annotations 

13 

14import logging 

15from typing import TYPE_CHECKING 

16 

17if TYPE_CHECKING: 

18 from nexusLIMS.db.models import Instrument 

19 from nexusLIMS.extractors.base import InstrumentProfile 

20 

21_logger = logging.getLogger(__name__) 

22 

23__all__ = [ 

24 "InstrumentProfileRegistry", 

25 "get_profile_registry", 

26] 

27 

28 

29class InstrumentProfileRegistry: 

30 """ 

31 Registry for instrument-specific extraction profiles. 

32 

33 Manages registration and lookup of InstrumentProfile objects, 

34 which customize extraction behavior for specific microscopes. 

35 

36 This is a singleton - use get_profile_registry() to access. 

37 

38 Examples 

39 -------- 

40 Register a profile: 

41 

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) 

53 

54 Retrieve a profile: 

55 

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 """ 

64 

65 def __init__(self): 

66 """Initialize the profile registry.""" 

67 self._profiles: dict[str, InstrumentProfile] = {} 

68 _logger.debug("Initialized InstrumentProfileRegistry") 

69 

70 def register(self, profile: InstrumentProfile) -> None: 

71 """ 

72 Register an instrument profile. 

73 

74 Parameters 

75 ---------- 

76 profile 

77 The profile to register 

78 

79 Raises 

80 ------ 

81 ValueError 

82 If a profile with the same instrument_id is already registered 

83 

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 ) 

96 

97 self._profiles[profile.instrument_id] = profile 

98 _logger.debug("Registered profile for: %s", profile.instrument_id) 

99 

100 def get_profile(self, instrument: Instrument | None) -> InstrumentProfile | None: 

101 """ 

102 Get the profile for a specific instrument. 

103 

104 Parameters 

105 ---------- 

106 instrument 

107 The instrument to look up, or None 

108 

109 Returns 

110 ------- 

111 InstrumentProfile | None 

112 The profile for this instrument, or None if no profile registered 

113 

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 

129 

130 instrument_id = instrument.name 

131 return self._profiles.get(instrument_id) 

132 

133 def get_all_profiles(self) -> dict[str, InstrumentProfile]: 

134 """ 

135 Get all registered profiles. 

136 

137 Returns 

138 ------- 

139 dict[str, InstrumentProfile] 

140 Dictionary mapping instrument IDs to profiles 

141 

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() 

150 

151 def clear(self) -> None: 

152 """ 

153 Clear all registered profiles. 

154 

155 Primarily used for testing. 

156 

157 Examples 

158 -------- 

159 >>> registry = get_profile_registry() 

160 >>> registry.clear() 

161 """ 

162 self._profiles.clear() 

163 _logger.debug("Cleared all instrument profiles") 

164 

165 

166# Singleton instance 

167_profile_registry: InstrumentProfileRegistry | None = None 

168 

169 

170def get_profile_registry() -> InstrumentProfileRegistry: 

171 """ 

172 Get the global instrument profile registry (singleton). 

173 

174 Returns 

175 ------- 

176 InstrumentProfileRegistry 

177 The global profile registry instance 

178 

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