@@ -225,21 +225,65 @@ def check_consistent(self, check_runnable: bool = True) -> None:
225225 ' problems were found:\n - '
226226 + '\n - ' .join (errors ))
227227
228- def top_models (self ) -> List [Model ]:
228+ def root_model (self , selected_model : Optional [Reference ] = None ) -> Model :
229+ """Return the root model of this configuration.
230+
231+ If there are multiple models that are not used as an implementation in any
232+ component (root models), and selected_model is given and names one of them, then
233+ that model is returned, otherwise an exception is raised.
234+
235+ If there is only a single root model, then it is returned, unless selected_model
236+ is given and does not match, in which case an exception is raised.
237+
238+ If there are no models, an exception is raised.
239+
240+ Args:
241+ selected_model: Name of the model to return, in case of multiple options
242+
243+ Returns:
244+ The sole or selected model that is not used as an implementation in any
245+ component in the configuration.
246+
247+ Raises:
248+ RuntimeError if an error occurs, as described above.
249+ """
250+ root_models = self ._root_models ()
251+ if not root_models :
252+ raise RuntimeError ('No model was found in this configuration.' )
253+
254+ if selected_model :
255+ match = [m for m in root_models if m .name == selected_model ]
256+ if match :
257+ return match [0 ]
258+
259+ models = '\n - ' .join ([str (m .name ) for m in root_models ])
260+ raise RuntimeError (
261+ f'The selected model "{ selected_model } " could not be found in this'
262+ ' configuration. The following models are present:\n - {models}' )
263+
264+ if len (root_models ) == 1 :
265+ return root_models [0 ]
266+
267+ models = '\n - ' .join ([str (m .name ) for m in root_models ])
268+ raise RuntimeError (
269+ 'Multiple models were found in this configuration that are not used'
270+ f' as implementations:\n \n - { models } ' )
271+
272+ def _root_models (self ) -> List [Model ]:
229273 """Models in this configuration that are not used as implementations."""
230- top_models = copy (self .models )
274+ root_models = copy (self .models )
231275
232276 for model in self .models .values ():
233277 for component in model .components .values ():
234278 if component .implementation :
235- if component .implementation in top_models :
236- del top_models [component .implementation ]
279+ if component .implementation in root_models :
280+ del root_models [component .implementation ]
237281
238282 for impl in self .custom_implementations .values ():
239- if impl in top_models :
240- del top_models [impl ]
283+ if impl in root_models :
284+ del root_models [impl ]
241285
242- return list (top_models .values ())
286+ return list (root_models .values ())
243287
244288 def _component_paths (self ) -> Dict [Reference , Component ]:
245289 """Return component paths for components.
@@ -253,7 +297,7 @@ def _component_paths(self) -> Dict[Reference, Component]:
253297 same component object, if a submodel is used multiple times.
254298 """
255299 result = dict ()
256- queue = [(m , Reference ([])) for m in self .top_models ()]
300+ queue = [(m , Reference ([])) for m in self ._root_models ()]
257301
258302 while queue :
259303 model , prefix = queue .pop (0 )
0 commit comments