diff --git a/python/ribasim/ribasim/input_base.py b/python/ribasim/ribasim/input_base.py index d2b2b5f86..bae11f35a 100644 --- a/python/ribasim/ribasim/input_base.py +++ b/python/ribasim/ribasim/input_base.py @@ -316,6 +316,17 @@ def columns(cls) -> list[str]: else: return [] + def __repr__(self) -> str: + # Make sure not to return just "None", because it gets extremely confusing + # when debugging. + return f"{self.tablename()}\n{self.df.__repr__()}" + + def _repr_html_(self): + if self.df is None: + return self.__repr__() + else: + return f"
{self.tablename()}
" + self.df._repr_html_() + class SpatialTableModel(TableModel[TableT], Generic[TableT]): @classmethod @@ -394,3 +405,23 @@ def _save(self, directory: DirectoryPath, input_dir: DirectoryPath, **kwargs): input_dir, sort_keys=self._sort_keys.get("field", ["node_id"]), ) + + def _repr_content(self) -> str: + """Generate a succinct overview of the content. + + Skip "empty" attributes: when the dataframe of a TableModel is None. + """ + content = [] + for field in self.fields(): + attr = getattr(self, field) + if isinstance(attr, TableModel): + if attr.df is not None: + content.append(field) + else: + content.append(field) + return ", ".join(content) + + def __repr__(self) -> str: + content = self._repr_content() + typename = type(self).__name__ + return f"{typename}({content})" diff --git a/python/ribasim/ribasim/model.py b/python/ribasim/ribasim/model.py index 86c8cf4a0..d51a21e48 100644 --- a/python/ribasim/ribasim/model.py +++ b/python/ribasim/ribasim/model.py @@ -38,7 +38,7 @@ ) from ribasim.geometry.edge import Edge from ribasim.geometry.node import Node -from ribasim.input_base import FileModel, NodeModel, TableModel, context_file_loading +from ribasim.input_base import FileModel, NodeModel, context_file_loading from ribasim.types import FilePath @@ -206,20 +206,24 @@ def serialize_path(self, path: Path) -> str: return str(path) def __repr__(self) -> str: - first = [] - second = [] + """Generate a succinct overview of the Model content. + + Skip "empty" NodeModel instances: when all dataframes are None. + """ + content = ["ribasim.Model("] + INDENT = " " for field in self.fields(): attr = getattr(self, field) - if isinstance(attr, TableModel): - second.append(f"{field}: {repr(attr)}") + if isinstance(attr, NodeModel): + attr_content = attr._repr_content() + typename = type(attr).__name__ + if attr_content: + content.append(f"{INDENT}{field}={typename}({attr_content}),") else: - first.append(f"{field}={repr(attr)}") - content = [""] + first + second - return "\n".join(content) + content.append(f"{INDENT}{field}={repr(attr)},") - def _repr_html(self): - # Default to standard repr for now - return self.__repr__() + content.append(")") + return "\n".join(content) def _write_toml(self, directory: FilePath): directory = Path(directory) diff --git a/python/ribasim/tests/test_io.py b/python/ribasim/tests/test_io.py index 129a01963..1d3335276 100644 --- a/python/ribasim/tests/test_io.py +++ b/python/ribasim/tests/test_io.py @@ -72,8 +72,13 @@ def test_repr(): ) pump_1 = Pump(static=static_data) + pump_2 = Pump() - assert repr(pump_1) == "Pump(static=TableModel[PumpStaticSchema]())" + assert repr(pump_1) == "Pump(static)" + assert repr(pump_2) == "Pump()" + # Ensure _repr_html doesn't error + assert isinstance(pump_1.static._repr_html_(), str) + assert isinstance(pump_2.static._repr_html_(), str) def test_extra_columns(): diff --git a/python/ribasim/tests/test_model.py b/python/ribasim/tests/test_model.py index e8f5baa77..0138e1e12 100644 --- a/python/ribasim/tests/test_model.py +++ b/python/ribasim/tests/test_model.py @@ -9,7 +9,7 @@ def test_repr(basic): representation = repr(basic).split("\n") - assert representation[0] == "" + assert representation[0] == "ribasim.Model(" def test_solver():