Coverage for backend/ahuora-compounds/ahuora_compounds/CompoundRegistry.py: 42%

54 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-26 20:57 +0000

1from typing import Dict, Set 

2from ahuora_compounds.Compound import Compound 

3from ahuora_compounds.PropertyPackage import PropertyPackage 

4 

5 

6class CompoundRegistry: 

7 

8 def __init__(self): 

9 self._compounds: Dict[str, Compound] = {} 

10 self._packages: Dict[str, PropertyPackage] = {} 

11 

12 @property 

13 def compounds(self) -> Dict[str, Compound]: 

14 return self._compounds 

15 

16 @property 

17 def packages(self) -> Dict[str, PropertyPackage]: 

18 return self._packages 

19 

20 # ------------------------------------------------------------------ # 

21 # Write API — called directly by package registration functions # 

22 # ------------------------------------------------------------------ # 

23 

24 def register_package(self, package: PropertyPackage) -> None: 

25 """ 

26 Register a new property package in the registry. 

27 

28 Args: 

29 package (PropertyPackage): The property package to register. 

30 

31 Raises: 

32 ValueError: If a package with the same name is already registered. 

33 """ 

34 if package.name in self._packages: 34 ↛ 35line 34 didn't jump to line 35 because the condition on line 34 was never true

35 raise ValueError(f"Package {package.name} is already registered.") 

36 self._packages[package.name] = package 

37 

38 def register_compound(self, name: str) -> None: 

39 """ 

40 Register a compound (or add a new source to an existing one). 

41 

42 Args: 

43 name (str): Name of the compound. 

44 """ 

45 if name not in self._compounds: 

46 self._compounds[name] = Compound(name) 

47 

48 def bind(self, compound_name: str, package_name: str, phases: list[str] = ["Liq","Vap"]) -> None: 

49 """ 

50 Bind a compound to a property package. 

51 

52 Registers the compound with the package and the package with the compound. 

53 

54 Args: 

55 compound_name (str): Name of the compound to bind. 

56 package_name (str): Name of the property package to bind to. 

57 

58 Raises: 

59 ValueError: If either the compound or the package is not registered. 

60 """ 

61 self.register_compound(compound_name) 

62 if package_name not in self._packages: 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true

63 raise ValueError(f"Package {package_name} is not registered.") 

64 

65 self._packages[package_name].register_compound(compound_name, phases=phases) 

66 

67 

68 # ------------------------------------------------------------------ # 

69 # Read API — called by RegistrySearch # 

70 # ------------------------------------------------------------------ # 

71 

72 def _get_package(self, package_name: str) -> PropertyPackage | None: 

73 return self._packages.get(package_name, None) 

74 

75 def _get_compound(self, compound_name: str) -> Compound | None: 

76 """ 

77 Get a compound by its name. 

78 

79 Args: 

80 compound_name (str): Name of the compound to retrieve. 

81 

82 Returns: 

83 Compound: The compound object if found, otherwise None. 

84 """ 

85 return self._compounds.get(compound_name, None) 

86 

87 def _get_compound_names(self) -> list[str]: 

88 """ 

89 Returns a list of all compound names. 

90 

91 Returns: 

92 list[str]: List of compound names. 

93 """ 

94 return list(self._compounds.keys()) 

95 

96 def _get_supported_packages(self, compounds: Set[str], strict: bool = True) -> set[str]: 

97 """ 

98 Get the set of property packages that support the given compounds. 

99 

100 Args: 

101 compounds (Set[str]): Compound names to check. 

102 strict (bool): If True, all compounds must be supported. 

103 If False, at least one compound must be supported. 

104 

105 Returns: 

106 set[str]: Package names that match the criteria. 

107 """ 

108 supported_packages = set() 

109 for package in self._packages.values(): 

110 if package.check_supported_compounds(compounds, strict): 

111 supported_packages.add(package.name) 

112 return supported_packages 

113 

114 def _get_supported_compounds(self, packages: Set[str], strict: bool = True) -> set[str]: 

115 """ 

116 Get the set of compounds supported by the given property packages. 

117 

118 Args: 

119 packages (Set[str]): Package names to check. 

120 strict (bool): If True, all packages must support each compound. 

121 If False, at least one package must support each compound. 

122 

123 Returns: 

124 set[str]: Compound names that match the criteria. 

125 

126 Raises: 

127 ValueError: If any of the given package names is not registered. 

128 """ 

129 supported_compounds: set[str] = set() 

130 for package_name in packages: 

131 p = self._get_package(package_name) 

132 if p is None: 

133 raise ValueError(f"Package {package_name} is not registered.") 

134 if strict: 

135 if len(supported_compounds) == 0: 

136 supported_compounds = p.compounds 

137 else: 

138 supported_compounds = supported_compounds.intersection(p.compounds) 

139 else: 

140 supported_compounds = supported_compounds.union(p.compounds) 

141 return supported_compounds 

142 

143 def _search_compounds(self, 

144 query: str, 

145 package_filters: Set[str] | None = None, 

146 filter_strict: bool = False) -> set[str]: 

147 """ 

148 Search for compounds whose name contains the query string. 

149 

150 Args: 

151 query (str): Substring to search for (case-insensitive). 

152 package_filters (Set[str] | None): If provided, only compounds 

153 supported by these packages are considered. 

154 filter_strict (bool): If True, all packages must support each 

155 returned compound. If False, at least one package suffices. 

156 

157 Returns: 

158 set[str]: Matching compound names. 

159 """ 

160 matched = {name for name in self._compounds if query.lower() in name.lower()} 

161 if package_filters is not None: 

162 matched = self._get_supported_compounds(package_filters, strict=filter_strict) \ 

163 .intersection(matched) 

164 return matched 

165