floodmodeller-api 0.5.0.post1__py3-none-any.whl → 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- floodmodeller_api/__init__.py +11 -1
- floodmodeller_api/_base.py +55 -36
- floodmodeller_api/backup.py +15 -12
- floodmodeller_api/dat.py +191 -121
- floodmodeller_api/diff.py +4 -4
- floodmodeller_api/hydrology_plus/hydrology_plus_export.py +15 -14
- floodmodeller_api/ied.py +8 -10
- floodmodeller_api/ief.py +56 -42
- floodmodeller_api/ief_flags.py +1 -1
- floodmodeller_api/inp.py +7 -10
- floodmodeller_api/logs/lf.py +25 -26
- floodmodeller_api/logs/lf_helpers.py +20 -20
- floodmodeller_api/logs/lf_params.py +1 -5
- floodmodeller_api/mapping.py +11 -2
- floodmodeller_api/test/__init__.py +2 -2
- floodmodeller_api/test/conftest.py +2 -3
- floodmodeller_api/test/test_backup.py +2 -2
- floodmodeller_api/test/test_conveyance.py +13 -7
- floodmodeller_api/test/test_dat.py +168 -20
- floodmodeller_api/test/test_data/EX18_DAT_expected.json +164 -144
- floodmodeller_api/test/test_data/EX3_DAT_expected.json +6 -2
- floodmodeller_api/test/test_data/EX6_DAT_expected.json +12 -46
- floodmodeller_api/test/test_data/encoding_test_cp1252.dat +1081 -0
- floodmodeller_api/test/test_data/encoding_test_utf8.dat +1081 -0
- floodmodeller_api/test/test_data/integrated_bridge/AR_NoSP_NoBl_2O_NO_OneFRC.ied +33 -0
- floodmodeller_api/test/test_data/integrated_bridge/AR_vSP_25pc_1O.ied +32 -0
- floodmodeller_api/test/test_data/integrated_bridge/PL_vSP_25pc_1O.ied +34 -0
- floodmodeller_api/test/test_data/integrated_bridge/SBTwoFRCsStaggered.IED +32 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_NoSP_NoBl_OR_RN.ied +28 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_SP_NoBl_OR_frc_PT2-5_RN.ied +34 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_fSP_NoBl_1O.ied +30 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_nSP_NoBl_1O.ied +49 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_vSP_NoBl_2O_Para.ied +35 -0
- floodmodeller_api/test/test_data/integrated_bridge.dat +40 -0
- floodmodeller_api/test/test_data/network.ied +2 -2
- floodmodeller_api/test/test_data/network_dat_expected.json +141 -243
- floodmodeller_api/test/test_data/network_ied_expected.json +2 -2
- floodmodeller_api/test/test_data/network_with_comments.ied +2 -2
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.json +69 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.csv +20 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.json +292 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.json +35 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_flow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_fr.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_stage.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_state.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_velocity.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_link_inflow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_max.csv +87 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_mode.csv +182 -0
- floodmodeller_api/test/test_flowtimeprofile.py +2 -2
- floodmodeller_api/test/test_hydrology_plus_export.py +4 -2
- floodmodeller_api/test/test_ied.py +3 -3
- floodmodeller_api/test/test_ief.py +12 -4
- floodmodeller_api/test/test_inp.py +2 -2
- floodmodeller_api/test/test_integrated_bridge.py +159 -0
- floodmodeller_api/test/test_json.py +14 -13
- floodmodeller_api/test/test_logs_lf.py +50 -29
- floodmodeller_api/test/test_read_file.py +1 -0
- floodmodeller_api/test/test_river.py +12 -12
- floodmodeller_api/test/test_tool.py +8 -5
- floodmodeller_api/test/test_toolbox_structure_log.py +148 -158
- floodmodeller_api/test/test_xml2d.py +14 -16
- floodmodeller_api/test/test_zz.py +143 -0
- floodmodeller_api/to_from_json.py +9 -9
- floodmodeller_api/tool.py +15 -11
- floodmodeller_api/toolbox/example_tool.py +5 -1
- floodmodeller_api/toolbox/model_build/add_siltation_definition.py +13 -9
- floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +500 -194
- floodmodeller_api/toolbox/model_build/structure_log_definition.py +5 -1
- floodmodeller_api/units/__init__.py +15 -0
- floodmodeller_api/units/_base.py +87 -20
- floodmodeller_api/units/_helpers.py +343 -0
- floodmodeller_api/units/boundaries.py +59 -71
- floodmodeller_api/units/comment.py +1 -1
- floodmodeller_api/units/conduits.py +57 -54
- floodmodeller_api/units/connectors.py +112 -0
- floodmodeller_api/units/controls.py +107 -0
- floodmodeller_api/units/conveyance.py +1 -1
- floodmodeller_api/units/iic.py +2 -9
- floodmodeller_api/units/losses.py +44 -45
- floodmodeller_api/units/sections.py +52 -51
- floodmodeller_api/units/structures.py +361 -531
- floodmodeller_api/units/units.py +27 -26
- floodmodeller_api/units/unsupported.py +5 -7
- floodmodeller_api/units/variables.py +2 -2
- floodmodeller_api/urban1d/_base.py +13 -17
- floodmodeller_api/urban1d/conduits.py +11 -21
- floodmodeller_api/urban1d/general_parameters.py +1 -1
- floodmodeller_api/urban1d/junctions.py +7 -11
- floodmodeller_api/urban1d/losses.py +13 -17
- floodmodeller_api/urban1d/outfalls.py +18 -22
- floodmodeller_api/urban1d/raingauges.py +5 -10
- floodmodeller_api/urban1d/subsections.py +5 -4
- floodmodeller_api/urban1d/xsections.py +14 -17
- floodmodeller_api/util.py +23 -6
- floodmodeller_api/validation/parameters.py +7 -3
- floodmodeller_api/validation/urban_parameters.py +1 -4
- floodmodeller_api/validation/validation.py +11 -5
- floodmodeller_api/version.py +1 -1
- floodmodeller_api/xml2d.py +27 -31
- floodmodeller_api/xml2d_template.py +1 -1
- floodmodeller_api/zz.py +539 -0
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/LICENSE.txt +1 -1
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/METADATA +30 -16
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/RECORD +116 -83
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/WHEEL +1 -1
- floodmodeller_api/test/test_zzn.py +0 -36
- floodmodeller_api/units/helpers.py +0 -123
- floodmodeller_api/zzn.py +0 -414
- /floodmodeller_api/test/test_data/{network_from_tabularCSV.csv → tabular_csv_outputs/network_zzn_max.csv} +0 -0
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/entry_points.txt +0 -0
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/top_level.txt +0 -0
floodmodeller_api/__init__.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
1
4
|
from .dat import DAT
|
|
2
5
|
from .ied import IED
|
|
3
6
|
from .ief import IEF
|
|
@@ -6,4 +9,11 @@ from .logs import LF1, LF2
|
|
|
6
9
|
from .util import read_file
|
|
7
10
|
from .version import __version__
|
|
8
11
|
from .xml2d import XML2D
|
|
9
|
-
from .
|
|
12
|
+
from .zz import ZZN, ZZX
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
stream=sys.stdout,
|
|
16
|
+
level=logging.INFO,
|
|
17
|
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
|
18
|
+
datefmt="%H:%M:%S",
|
|
19
|
+
)
|
floodmodeller_api/_base.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Flood Modeller Python API
|
|
3
|
-
Copyright (C)
|
|
3
|
+
Copyright (C) 2025 Jacobs U.K. Limited
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
|
|
6
6
|
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
@@ -18,6 +18,7 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
""" Holds the base file class for API file classes """
|
|
20
20
|
|
|
21
|
+
import logging
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from typing import NoReturn
|
|
23
24
|
|
|
@@ -33,25 +34,26 @@ from .util import FloodModellerAPIError, handle_exception
|
|
|
33
34
|
class FMFile(Jsonable):
|
|
34
35
|
"""Base class for all Flood Modeller File types"""
|
|
35
36
|
|
|
36
|
-
_filetype: str
|
|
37
|
-
_suffix: str
|
|
37
|
+
_filetype: str
|
|
38
|
+
_suffix: str
|
|
38
39
|
MAX_DIFF = 25
|
|
40
|
+
ENCODING = "cp1252"
|
|
39
41
|
|
|
40
42
|
def __init__(self, filepath: str | Path | None = None, **kwargs):
|
|
41
43
|
if filepath is not None:
|
|
42
44
|
self._filepath = Path(filepath)
|
|
43
45
|
# * Add check or fix for path lengths greater than DOS standard length of 260 characters
|
|
44
46
|
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
)
|
|
47
|
+
if self._filepath.suffix.lower() != self._suffix:
|
|
48
|
+
msg = f"Given filepath does not point to a {self._filetype} file. Please point to the full path for a {self._filetype} file"
|
|
49
|
+
raise TypeError(msg)
|
|
49
50
|
if not self._filepath.exists():
|
|
50
|
-
|
|
51
|
+
msg = (
|
|
51
52
|
f"{self._filetype} file does not exist! If you are wanting to create a new {self._filetype}, initiate the class without a given "
|
|
52
53
|
f"filepath to create a new blank {self._filetype} or point the filepath of an existing {self._filetype} to use as a template, "
|
|
53
|
-
f"then use the .save() method to save to a new filepath"
|
|
54
|
+
f"then use the .save() method to save to a new filepath"
|
|
54
55
|
)
|
|
56
|
+
raise FileNotFoundError(msg)
|
|
55
57
|
# If the file is not a ZZN file, then perform a backup
|
|
56
58
|
# This performs a conditional back up, only copying the file if an equivalent copy doesn't already exist
|
|
57
59
|
if self._filetype != "ZZN":
|
|
@@ -60,6 +62,17 @@ class FMFile(Jsonable):
|
|
|
60
62
|
# Add the file object as a property to expose the restore method
|
|
61
63
|
self.file = file
|
|
62
64
|
|
|
65
|
+
@property
|
|
66
|
+
def filepath(self) -> Path:
|
|
67
|
+
if not hasattr(self, "_filepath"):
|
|
68
|
+
msg = "Object has no filepath."
|
|
69
|
+
raise AttributeError(msg)
|
|
70
|
+
return self._filepath
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def filetype(self) -> str:
|
|
74
|
+
return self._filetype
|
|
75
|
+
|
|
63
76
|
def __repr__(self):
|
|
64
77
|
filepath = "<in_memory>" if not hasattr(self, "_filepath") else self._filepath
|
|
65
78
|
return f"<floodmodeller_api Class: {self._filetype}(filepath={filepath})>"
|
|
@@ -73,59 +86,65 @@ class FMFile(Jsonable):
|
|
|
73
86
|
def _update(self):
|
|
74
87
|
"""Updates the existing self._filetype based on any altered attributes"""
|
|
75
88
|
if self._filepath is None:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
89
|
+
msg = f"{self._filetype} must be saved to a specific filepath before update() can be called."
|
|
90
|
+
raise UserWarning(msg)
|
|
79
91
|
|
|
80
92
|
string = self._write()
|
|
81
|
-
with open(self._filepath, "w") as _file:
|
|
93
|
+
with open(self._filepath, "w", encoding=self.ENCODING) as _file:
|
|
82
94
|
_file.write(string)
|
|
83
|
-
|
|
95
|
+
logging.info("%s File Updated!", self._filepath)
|
|
84
96
|
|
|
85
97
|
def _save(self, filepath):
|
|
86
98
|
filepath = Path(filepath).absolute()
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
99
|
+
if filepath.suffix.lower() != self._suffix:
|
|
100
|
+
msg = f'Given filepath does not point to a filepath suffixed "{self._suffix}". Please point to the full path to save the {self._filetype} file'
|
|
101
|
+
raise TypeError(msg)
|
|
91
102
|
|
|
92
103
|
if not filepath.parent.exists():
|
|
93
104
|
Path.mkdir(filepath.parent)
|
|
94
105
|
|
|
95
106
|
string = self._write()
|
|
96
|
-
with open(filepath, "w") as _file:
|
|
107
|
+
with open(filepath, "w", encoding=self.ENCODING) as _file:
|
|
97
108
|
_file.write(string)
|
|
98
109
|
self._filepath = filepath # Updates the filepath attribute to the given path
|
|
99
110
|
|
|
100
|
-
|
|
111
|
+
logging.info("%s File Saved to: %s", self._filetype, filepath)
|
|
101
112
|
|
|
102
113
|
@handle_exception(when="compare")
|
|
103
|
-
def _diff(self, other, force_print=False):
|
|
114
|
+
def _diff(self, other, force_print=False) -> None:
|
|
115
|
+
def _format_diff(diff_list, max_items=None) -> str:
|
|
116
|
+
return "\n".join(
|
|
117
|
+
f" {name}: {reason}"
|
|
118
|
+
for name, reason in (diff_list[:max_items] if max_items else diff_list)
|
|
119
|
+
)
|
|
120
|
+
|
|
104
121
|
if self._filetype != other._filetype:
|
|
105
|
-
|
|
122
|
+
msg = "Cannot compare objects of different filetypes"
|
|
123
|
+
raise TypeError(msg)
|
|
106
124
|
diff = self._get_diff(other)
|
|
107
125
|
if diff[0]:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
print("\n...To see full list of all differences add force_print=True")
|
|
119
|
-
else:
|
|
120
|
-
print("\n".join([f" {name}: {reason}" for name, reason in diff[1]]))
|
|
126
|
+
logging.info("No difference, files are equivalent")
|
|
127
|
+
return
|
|
128
|
+
differences = (
|
|
129
|
+
f"[Showing first {self.MAX_DIFF} differences...]\n"
|
|
130
|
+
f"{_format_diff(diff[1], self.MAX_DIFF)}\n"
|
|
131
|
+
"...To see full list of all differences add force_print=True"
|
|
132
|
+
if len(diff[1]) > self.MAX_DIFF and not force_print
|
|
133
|
+
else _format_diff(diff[1])
|
|
134
|
+
)
|
|
135
|
+
logging.info("Files not equivalent, %s difference(s) found:\n%s", len(diff[1]), differences)
|
|
121
136
|
|
|
122
137
|
def _get_diff(self, other):
|
|
123
138
|
return self.__eq__(other, return_diff=True) # pylint: disable=unnecessary-dunder-call
|
|
124
139
|
|
|
125
140
|
def _handle_exception(self, err, when) -> NoReturn:
|
|
126
|
-
|
|
141
|
+
filepath_or_none = self._filepath if hasattr(self, "_filepath") else None
|
|
142
|
+
raise FloodModellerAPIError(err, when, self._filetype, filepath_or_none) from err
|
|
127
143
|
|
|
128
144
|
def __eq__(self, other, return_diff=False):
|
|
145
|
+
if not isinstance(other, FMFile):
|
|
146
|
+
return NotImplemented if not return_diff else (False, ["Type mismatch"])
|
|
147
|
+
|
|
129
148
|
result = True
|
|
130
149
|
diff = []
|
|
131
150
|
try:
|
floodmodeller_api/backup.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Flood Modeller Python API
|
|
3
|
-
Copyright (C)
|
|
3
|
+
Copyright (C) 2025 Jacobs U.K. Limited
|
|
4
4
|
|
|
5
5
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
|
|
6
6
|
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
@@ -17,6 +17,7 @@ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import filecmp
|
|
20
|
+
import logging
|
|
20
21
|
import re
|
|
21
22
|
import tempfile
|
|
22
23
|
from datetime import datetime
|
|
@@ -60,6 +61,8 @@ class BackupControl(Jsonable):
|
|
|
60
61
|
backup.clear_backup()
|
|
61
62
|
"""
|
|
62
63
|
|
|
64
|
+
ENCODING = "cp1252"
|
|
65
|
+
|
|
63
66
|
def __init__(self):
|
|
64
67
|
"""
|
|
65
68
|
Initialises a new BackUp object.
|
|
@@ -80,11 +83,15 @@ class BackupControl(Jsonable):
|
|
|
80
83
|
# Create the backup directory if it doesn't exist
|
|
81
84
|
if not self.backup_dir.exists():
|
|
82
85
|
self.backup_dir.mkdir()
|
|
83
|
-
|
|
86
|
+
logging.info(
|
|
87
|
+
"%s: Initialised backup directory at %s",
|
|
88
|
+
self.__class__.__name__,
|
|
89
|
+
self.backup_dir,
|
|
90
|
+
)
|
|
84
91
|
|
|
85
92
|
# Create the backup CSV file if it doesn't exist
|
|
86
93
|
if not self.backup_csv_path.exists():
|
|
87
|
-
with open(self.backup_csv_path, "w") as f:
|
|
94
|
+
with open(self.backup_csv_path, "w", encoding=self.ENCODING) as f:
|
|
88
95
|
f.write("path,file_id,dttm\n")
|
|
89
96
|
|
|
90
97
|
def clear_backup(self, file_id="*"):
|
|
@@ -191,11 +198,11 @@ class File(BackupControl):
|
|
|
191
198
|
"""
|
|
192
199
|
|
|
193
200
|
def __init__(self, path: str | Path = "", from_json: bool = False, **args):
|
|
194
|
-
# TODO: Make protected properties so they can't be manipulated
|
|
195
201
|
self.path = Path(path)
|
|
196
|
-
# Check
|
|
202
|
+
# Check if the file exists
|
|
197
203
|
if not self.path.exists():
|
|
198
|
-
|
|
204
|
+
msg = "File not found!"
|
|
205
|
+
raise OSError(msg)
|
|
199
206
|
self.ext = self.path.suffix
|
|
200
207
|
self.dttm_str = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
|
201
208
|
self._generate_file_id()
|
|
@@ -211,7 +218,6 @@ class File(BackupControl):
|
|
|
211
218
|
Generate the file's unique identifier as using a hash of the absolute file path
|
|
212
219
|
"""
|
|
213
220
|
# hash the absolute path becuase the same file name / directroy structure may be mirrored across projects
|
|
214
|
-
# TODO: Use a function that produces a shorter has to make interpretation of the directory easier
|
|
215
221
|
fp_bytes = str(self.path.absolute()).encode()
|
|
216
222
|
self.file_id = sha1(fp_bytes).hexdigest()
|
|
217
223
|
|
|
@@ -231,9 +237,8 @@ class File(BackupControl):
|
|
|
231
237
|
backup_filepath = Path(self.backup_dir, self.backup_filename)
|
|
232
238
|
copy(self.path, backup_filepath)
|
|
233
239
|
# Log an entry to the csv to make it easy to find the file
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
with open(self.backup_csv_path, "a") as f:
|
|
240
|
+
log_str = f"{self.path!s},{self.file_id},{self.dttm_str}\n"
|
|
241
|
+
with open(self.backup_csv_path, "a", encoding=self.ENCODING) as f:
|
|
237
242
|
f.write(log_str)
|
|
238
243
|
|
|
239
244
|
def list_backups(self) -> list:
|
|
@@ -258,8 +263,6 @@ class File(BackupControl):
|
|
|
258
263
|
if len(backups) == 0 or not filecmp.cmp(self.path, backups[0].path):
|
|
259
264
|
self._make_backup()
|
|
260
265
|
# If the file doesn't match the last backup then do a back up
|
|
261
|
-
# TODO: Use FloodModeller API implemented equivalence testing. This is implemented at a higher level than FMFile where this method is called.
|
|
262
|
-
# TODO: Return the file path?
|
|
263
266
|
|
|
264
267
|
def clear_backup(self):
|
|
265
268
|
"""
|