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
« 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
6class CompoundRegistry:
8 def __init__(self):
9 self._compounds: Dict[str, Compound] = {}
10 self._packages: Dict[str, PropertyPackage] = {}
12 @property
13 def compounds(self) -> Dict[str, Compound]:
14 return self._compounds
16 @property
17 def packages(self) -> Dict[str, PropertyPackage]:
18 return self._packages
20 # ------------------------------------------------------------------ #
21 # Write API — called directly by package registration functions #
22 # ------------------------------------------------------------------ #
24 def register_package(self, package: PropertyPackage) -> None:
25 """
26 Register a new property package in the registry.
28 Args:
29 package (PropertyPackage): The property package to register.
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
38 def register_compound(self, name: str) -> None:
39 """
40 Register a compound (or add a new source to an existing one).
42 Args:
43 name (str): Name of the compound.
44 """
45 if name not in self._compounds:
46 self._compounds[name] = Compound(name)
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.
52 Registers the compound with the package and the package with the compound.
54 Args:
55 compound_name (str): Name of the compound to bind.
56 package_name (str): Name of the property package to bind to.
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.")
65 self._packages[package_name].register_compound(compound_name, phases=phases)
68 # ------------------------------------------------------------------ #
69 # Read API — called by RegistrySearch #
70 # ------------------------------------------------------------------ #
72 def _get_package(self, package_name: str) -> PropertyPackage | None:
73 return self._packages.get(package_name, None)
75 def _get_compound(self, compound_name: str) -> Compound | None:
76 """
77 Get a compound by its name.
79 Args:
80 compound_name (str): Name of the compound to retrieve.
82 Returns:
83 Compound: The compound object if found, otherwise None.
84 """
85 return self._compounds.get(compound_name, None)
87 def _get_compound_names(self) -> list[str]:
88 """
89 Returns a list of all compound names.
91 Returns:
92 list[str]: List of compound names.
93 """
94 return list(self._compounds.keys())
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.
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.
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
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.
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.
123 Returns:
124 set[str]: Compound names that match the criteria.
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
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.
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.
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