Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/improve repr and autcomplete #76

Merged
merged 9 commits into from
Jan 23, 2024
4 changes: 2 additions & 2 deletions .github/workflows/full_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ jobs:
pip install .[test]
- name: Test with pytest (basic)
run: |
pytest
pytest -c .pytest-ci.ini
- name: Install mikeio1d (optional dependencies)
run: |
pip install .[all]
- name: Test with pytest (optional dependencies)
run: |
pytest
pytest -c .pytest-ci.ini
6 changes: 6 additions & 0 deletions .pytest-ci.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
; this is the pytest configuration file used by the CI pipeline
[pytest]
addopts =
--strict-markers
markers =
slow
7 changes: 7 additions & 0 deletions .pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
; this file is used locally and not in the CI pipeline - refer to .pytest-ci.ini for CI pipeline configuration
[pytest]
addopts =
--strict-markers
-m "not slow"
markers =
slow
46 changes: 34 additions & 12 deletions mikeio1d/res1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
from .query import QueryData
from .result_reader_writer.result_reader import ColumnMode

from .result_network import ResultCatchments
from .result_network import ResultNodes
from .result_network import ResultReaches
from .result_network import ResultStructures
from .result_network import ResultGlobalDatas

import os.path

from .dotnet import from_dotnet_datetime
Expand Down Expand Up @@ -118,35 +124,51 @@ def __init__(
self._end_time = None

self.result_network = ResultNetwork(self)
self.network = self.result_network
self.result_writer = ResultWriter(self)

self.clear_queue_after_reading = clear_queue_after_reading

self.network = self.result_network # alias
"""Network of the result file."""
self.catchments = self.result_network.catchments
self.reaches = self.result_network.reaches
"""Catchments of the result file."""
self.reaches: ResultReaches = self.result_network.reaches
"""Reaches of the result file."""
self.nodes = self.result_network.nodes
"""Nodes of the result file."""
self.structures = self.result_network.structures
"""Structures of the result file."""
self.global_data = self.result_network.global_data
"""Global data of the result file."""

def __repr__(self):
out = ["<mikeio1d.Res1D>"]
return "<mikeio1d.Res1D>"

def _get_info(self) -> info:
info = []
if self.file_path:
out.append(f"Start time: {str(self.start_time)}")
out.append(f"End time: {str(self.end_time)}")
out.append(f"# Timesteps: {str(self.data.NumberOfTimeSteps)}")
out.append(f"# Catchments: {self.data.Catchments.get_Count()}")
out.append(f"# Nodes: {self.data.Nodes.get_Count()}")
out.append(f"# Reaches: {self.data.Reaches.get_Count()}")

out.append(f"# Globals: {self.data.GlobalData.DataItems.Count}")
info.append(f"Start time: {str(self.start_time)}")
info.append(f"End time: {str(self.end_time)}")
info.append(f"# Timesteps: {str(self.data.NumberOfTimeSteps)}")
info.append(f"# Catchments: {self.data.Catchments.get_Count()}")
info.append(f"# Nodes: {self.data.Nodes.get_Count()}")
info.append(f"# Reaches: {self.data.Reaches.get_Count()}")

info.append(f"# Globals: {self.data.GlobalData.DataItems.Count}")
for i, quantity in enumerate(self.data.Quantities):
out.append(f"{i} - {quantity.Id} <{quantity.EumQuantity.UnitAbbreviation}>")
info.append(f"{i} - {quantity.Id} <{quantity.EumQuantity.UnitAbbreviation}>")

return str.join("\n", out)
info = str.join("\n", info)
return info

# region Private methods

def info(self):
"""Prints information about the result file."""
info = self._get_info()
print(info)

def _get_timeseries_ids_to_read(
self, queries: List[QueryData] | List[TimeSeriesId]
) -> List[TimeSeriesId]:
Expand Down
12 changes: 10 additions & 2 deletions mikeio1d/result_network/result_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,23 @@ def __repr__(self) -> str:
return f"<{self.__class__.__name__}>"

def _repr_html_(self) -> str:
attributes = {k: getattr(self, k) for k in self._static_attributes}
total_attributes = len(attributes)
total_quantities = len(self.quantities)
repr = build_html_repr_from_sections(
self.__repr__(),
[
("Attributes", {k: getattr(self, k) for k in self._static_attributes}),
("Quantities", list(self.result_quantity_map.keys())),
(f"Attributes ({total_attributes})", attributes),
(f"Quantities ({total_quantities})", self.quantities),
],
)
return repr

@property
def quantities(self) -> List[str]:
"""A list of available quantities."""
return list(self.result_quantity_map.keys())

def set_static_attribute(self, key, value):
"""Add static attribute. This shows up in the html repr"""
self._static_attributes.append(key)
Expand Down
45 changes: 44 additions & 1 deletion mikeio1d/result_network/result_locations.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from typing import Dict

if TYPE_CHECKING:
from typing import List
from .result_location import ResultLocation

from .result_location import ResultLocation

from ..dotnet import pythonnet_implementation as impl
from .result_quantity_collection import ResultQuantityCollection
from .various import make_proper_variable_name
from .various import build_html_repr_from_sections


class ResultLocations(dict):
class ResultLocations(Dict[str, ResultLocation]):
"""
A base class for a network locations (nodes, reaches)
or a catchments wrapper class.
Expand Down Expand Up @@ -33,6 +46,36 @@ def __init__(self, res1d):
self.data_items = res1d.data.DataItems
self.result_quantity_map = {}

def __repr__(self) -> str:
return f"<{self.__class__.__name__}>"

def _repr_html_(self) -> str:
total_names = len(self)
total_quantities = len(self.quantities)
repr = build_html_repr_from_sections(
self.__repr__(),
[
(f"Names ({total_names})", self.names),
(f"Quantities ({total_quantities})", list(self.quantities.keys())),
],
)
return repr

@property
def quantities(self) -> Dict[str, ResultQuantityCollection]:
"""A list of available quantities."""
return {k: getattr(self, k) for k in self.result_quantity_map}

@property
def names(self) -> List[str]:
"""A list of location names (e.g. MUIDs)."""
return list(self.keys())

@property
def locations(self) -> List[ResultLocation]:
"""A list of location objects (e.g. <ResultNode>)."""
return list(self.values())

def set_quantity_collections(self):
"""Sets all quantity collection attributes."""
for quantity_id in self.result_quantity_map:
Expand Down
8 changes: 8 additions & 0 deletions mikeio1d/result_network/result_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ def __init__(
self.m1d_dataset = m1d_dataset
self.element_index = element_index
self._timeseries_id: TimeSeriesId = None
self._name = data_item.Quantity.Id

def __repr__(self) -> str:
return f"<Quantity: {self.name}>"

@property
def name(self) -> str:
return self._name

def add(self):
"""Add a ResultQuantity to ResultNetwork.read_queue based on the data item."""
Expand Down
10 changes: 10 additions & 0 deletions mikeio1d/result_network/result_quantity_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ def __init__(self, result_quantities, res1d):
self.result_quantities = result_quantities
self.res1d = res1d

def __repr__(self) -> str:
return f"<QuantityCollection ({len(self.result_quantities)}): {self.name}>"

@property
def name(self) -> str:
"""Name of the quantity id assosciated with collection."""
if len(self.result_quantities) <= 0:
return "EMPTY"
return self.result_quantities[0].name

def add(self):
"""
Add queries to ResultNetwork.queries from a list of result quantities.
Expand Down
4 changes: 4 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def testdata_name():
return list(dataclasses.asdict(testdata).keys())


@pytest.mark.slow
@pytest.mark.parametrize("extension", [".res1d", ".res", ".resx", ".out"])
@pytest.mark.parametrize("result_reader", ["copier", "query"])
def test_mikeio1d_generates_expected_dataframe_for_filetype_read_all(result_reader, extension):
Expand Down Expand Up @@ -48,6 +49,7 @@ def sample_random_queries(res: Res1D) -> List[str]:
return sample_queries


@pytest.mark.slow
@pytest.mark.parametrize("extension", [".res1d", ".res", ".resx", ".out"])
@pytest.mark.parametrize("result_reader", ["copier", "query"])
def test_mikeio1d_generates_dataframe_reading_time_series_ids(result_reader, extension):
Expand All @@ -61,6 +63,7 @@ def test_mikeio1d_generates_dataframe_reading_time_series_ids(result_reader, ext
assert len(df) > 0


@pytest.mark.slow
@pytest.mark.parametrize("extension", [".res1d", ".res", ".resx", ".out"])
@pytest.mark.parametrize("result_reader", ["copier", "query"])
def test_mikeio1d_generates_dataframe_reading_queries(result_reader, extension):
Expand All @@ -74,6 +77,7 @@ def test_mikeio1d_generates_dataframe_reading_queries(result_reader, extension):
assert len(df) > 0


@pytest.mark.slow
@pytest.mark.parametrize("extension", [".res1d", ".res", ".resx", ".out"])
@pytest.mark.parametrize(
"column_mode", [ColumnMode.ALL, ColumnMode.COMPACT, ColumnMode.TIMESERIES, ColumnMode.STRING]
Expand Down
12 changes: 6 additions & 6 deletions tests/test_epanet_res_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ def test_quantities(test_file):
assert len(quantities) == 12


def test_repr(test_file):
def test_info(test_file):
epanet_res = test_file
epanet_res_repr = epanet_res.__repr__()
epanet_res_repr_ref = (
"<mikeio1d.Res1D>\n"
+ "Start time: 2022-10-13 00:00:00\n"
epanet_res_info = epanet_res._get_info()
epanet_res_info_ref = (
"Start time: 2022-10-13 00:00:00\n"
+ "End time: 2022-10-14 00:00:00\n"
+ "# Timesteps: 25\n"
+ "# Catchments: 0\n"
Expand All @@ -57,7 +56,8 @@ def test_repr(test_file):
+ "10 - ReactorRate <->\n"
+ "11 - FrictionFactor <->"
)
assert epanet_res_repr == epanet_res_repr_ref

assert epanet_res_info == epanet_res_info_ref


def test_data_item_dicts(test_file):
Expand Down
9 changes: 4 additions & 5 deletions tests/test_epanet_resx_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ def test_quantities(test_file):
assert len(quantities) == 5


def test_repr(test_file):
def test_info(test_file):
epanet_resx = test_file
epanet_resx_repr = epanet_resx.__repr__()
epanet_resx_info = epanet_resx._get_info()
epanet_repr_ref = (
"<mikeio1d.Res1D>\n"
+ "Start time: 2022-10-13 00:00:00\n"
"Start time: 2022-10-13 00:00:00\n"
+ "End time: 2022-10-14 00:00:00\n"
+ "# Timesteps: 25\n"
+ "# Catchments: 0\n"
Expand All @@ -50,7 +49,7 @@ def test_repr(test_file):
+ "3 - Pump energy costs </kWh>\n"
+ "4 - Pump energy <kW>"
)
assert epanet_resx_repr == epanet_repr_ref
assert epanet_resx_info == epanet_repr_ref


def test_data_item_dicts(test_file):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_network_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_single_node_html_repr(node):
padding-left: 2em;
}
</style>
<details><summary>Attributes</summary><ul><li>id: 1</li><li>type: Manhole</li><li>xcoord: -687934.6000976562</li><li>ycoord: -1056500.69921875</li><li>ground_level: 197.07000732421875</li><li>bottom_level: 195.0500030517578</li><li>critical_level: inf</li><li>diameter: 1.0</li></ul></details><details><summary>Quantities</summary><ul><li>WaterLevel</li></ul></details>"""
<details><summary>Attributes (8)</summary><ul><li>id: 1</li><li>type: Manhole</li><li>xcoord: -687934.6000976562</li><li>ycoord: -1056500.69921875</li><li>ground_level: 197.07000732421875</li><li>bottom_level: 195.0500030517578</li><li>critical_level: inf</li><li>diameter: 1.0</li></ul></details><details><summary>Quantities (1)</summary><ul><li>WaterLevel</li></ul></details>"""
assert html_repr == expected_html_repr


Expand All @@ -60,22 +60,22 @@ def test_single_catchment_html_repr(catchment):
padding-left: 2em;
}
</style>
<details><summary>Attributes</summary><ul><li>id: 100_16_16</li><li>area: 22800.0</li><li>type: Kinematic Wave</li></ul></details><details><summary>Quantities</summary><ul><li>TotalRunOff</li><li>ActualRainfall</li><li>ZinkLoadRR</li><li>ZinkMassAccumulatedRR</li><li>ZinkRR</li></ul></details>"""
<details><summary>Attributes (3)</summary><ul><li>id: 100_16_16</li><li>area: 22800.0</li><li>type: Kinematic Wave</li></ul></details><details><summary>Quantities (5)</summary><ul><li>TotalRunOff</li><li>ActualRainfall</li><li>ZinkLoadRR</li><li>ZinkMassAccumulatedRR</li><li>ZinkRR</li></ul></details>"""
assert html_repr == expected_html_repr


def test_single_reach_html_repr(river_reach):
html_repr = river_reach._repr_html_()
excepted_html_repr = """&lt;Reach: river&gt;
expected_html_repr = """&lt;Reach: river&gt;
<style>
ul {
margin: 0px;
padding: 0px;
padding-left: 2em;
}
</style>
<details><summary>Attributes</summary><ul><li>name: river</li><li>length: 2024.2276598819008</li><li>start_chainage: 53100.0</li><li>end_chainage: 55124.2276598819</li><li>n_gridpoints: 94</li></ul></details><details><summary>Quantities</summary><ul><li>WaterLevel</li><li>ManningResistanceNumber</li><li>Discharge</li><li>FlowVelocity</li></ul></details>"""
assert html_repr == excepted_html_repr
<details><summary>Attributes (5)</summary><ul><li>name: river</li><li>length: 2024.2276598819008</li><li>start_chainage: 53100.0</li><li>end_chainage: 55124.2276598819</li><li>n_gridpoints: 94</li></ul></details><details><summary>Quantities (4)</summary><ul><li>WaterLevel</li><li>ManningResistanceNumber</li><li>Discharge</li><li>FlowVelocity</li></ul></details>"""
assert html_repr == expected_html_repr


def test_single_structure_html_repr(structure):
Expand All @@ -88,5 +88,5 @@ def test_single_structure_html_repr(structure):
padding-left: 2em;
}
</style>
<details><summary>Attributes</summary><ul><li>id: 119w1</li><li>type: Weir</li><li>chainage: 0.5</li></ul></details><details><summary>Quantities</summary><ul><li>Discharge</li></ul></details>"""
<details><summary>Attributes (3)</summary><ul><li>id: 119w1</li><li>type: Weir</li><li>chainage: 0.5</li></ul></details><details><summary>Quantities (1)</summary><ul><li>Discharge</li></ul></details>"""
assert html_repr == expected_html_repr
11 changes: 5 additions & 6 deletions tests/test_res1d_catchments.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ def test_quantities(test_file):
assert len(quantities) == 5


def test_repr(test_file):
def test_info(test_file):
res1d = test_file
res1d_repr = res1d.__repr__()
res1d_repr_ref = (
"<mikeio1d.Res1D>\n"
+ "Start time: 1994-08-07 16:35:00\n"
res1d_info = res1d._get_info()
res1d_info_ref = (
"Start time: 1994-08-07 16:35:00\n"
+ "End time: 1994-08-07 18:35:00\n"
+ "# Timesteps: 108\n"
+ "# Catchments: 31\n"
Expand All @@ -51,7 +50,7 @@ def test_repr(test_file):
+ "3 - ZinkMassAccumulatedRR <kg>\n"
+ "4 - ZinkRR <mg/l>"
)
assert res1d_repr == res1d_repr_ref
assert res1d_info == res1d_info_ref


def test_data_item_dicts(test_file):
Expand Down
Loading