floodmodeller-api 0.4.2__py3-none-any.whl → 0.4.3__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.
Files changed (178) hide show
  1. floodmodeller_api/__init__.py +8 -9
  2. floodmodeller_api/_base.py +184 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +909 -838
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +307 -311
  7. floodmodeller_api/ief.py +647 -646
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +266 -268
  10. floodmodeller_api/libs/libifcoremd.dll +0 -0
  11. floodmodeller_api/libs/libifcoremt.so.5 +0 -0
  12. floodmodeller_api/libs/libifport.so.5 +0 -0
  13. floodmodeller_api/{libmmd.dll → libs/libimf.so} +0 -0
  14. floodmodeller_api/libs/libintlc.so.5 +0 -0
  15. floodmodeller_api/libs/libmmd.dll +0 -0
  16. floodmodeller_api/libs/libsvml.so +0 -0
  17. floodmodeller_api/libs/libzzn_read.so +0 -0
  18. floodmodeller_api/libs/zzn_read.dll +0 -0
  19. floodmodeller_api/logs/__init__.py +2 -2
  20. floodmodeller_api/logs/lf.py +320 -314
  21. floodmodeller_api/logs/lf_helpers.py +354 -346
  22. floodmodeller_api/logs/lf_params.py +643 -529
  23. floodmodeller_api/mapping.py +84 -0
  24. floodmodeller_api/test/__init__.py +4 -4
  25. floodmodeller_api/test/conftest.py +9 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_dat.py +221 -92
  28. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  29. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  30. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  31. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  33. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  34. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  35. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  36. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  37. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  38. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  39. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  40. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  41. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  42. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  46. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  47. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  48. floodmodeller_api/test/test_data/EX1.ext +107 -107
  49. floodmodeller_api/test/test_data/EX1.feb +320 -320
  50. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  51. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  52. floodmodeller_api/test/test_data/EX17.ext +213 -213
  53. floodmodeller_api/test/test_data/EX17.feb +422 -422
  54. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  55. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  56. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  57. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  58. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  59. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  60. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  61. floodmodeller_api/test/test_data/EX6.ext +532 -532
  62. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  63. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  64. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  65. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  66. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  67. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  68. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  69. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  70. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  71. floodmodeller_api/test/test_data/blockage.dat +50 -50
  72. floodmodeller_api/test/test_data/blockage.ext +45 -45
  73. floodmodeller_api/test/test_data/blockage.feb +9 -9
  74. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  75. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  76. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  77. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  78. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  79. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  80. floodmodeller_api/test/test_data/ex3.ief +20 -20
  81. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  82. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  83. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  84. floodmodeller_api/test/test_data/example1.inp +329 -329
  85. floodmodeller_api/test/test_data/example2.inp +158 -158
  86. floodmodeller_api/test/test_data/example3.inp +297 -297
  87. floodmodeller_api/test/test_data/example4.inp +388 -388
  88. floodmodeller_api/test/test_data/example5.inp +147 -147
  89. floodmodeller_api/test/test_data/example6.inp +154 -154
  90. floodmodeller_api/test/test_data/jump.dat +176 -176
  91. floodmodeller_api/test/test_data/network.dat +1374 -1374
  92. floodmodeller_api/test/test_data/network.ext +45 -45
  93. floodmodeller_api/test/test_data/network.exy +1 -1
  94. floodmodeller_api/test/test_data/network.feb +45 -45
  95. floodmodeller_api/test/test_data/network.ied +45 -45
  96. floodmodeller_api/test/test_data/network.ief +20 -20
  97. floodmodeller_api/test/test_data/network.inp +147 -147
  98. floodmodeller_api/test/test_data/network.pxy +57 -57
  99. floodmodeller_api/test/test_data/network.zzd +122 -122
  100. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  101. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  102. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  103. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  104. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  105. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  106. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  107. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  108. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  109. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  110. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  111. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  112. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  113. floodmodeller_api/test/test_ied.py +29 -29
  114. floodmodeller_api/test/test_ief.py +125 -24
  115. floodmodeller_api/test/test_inp.py +47 -48
  116. floodmodeller_api/test/test_json.py +114 -0
  117. floodmodeller_api/test/test_logs_lf.py +48 -51
  118. floodmodeller_api/test/test_tool.py +165 -154
  119. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  120. floodmodeller_api/test/test_xml2d.py +151 -156
  121. floodmodeller_api/test/test_zzn.py +36 -34
  122. floodmodeller_api/to_from_json.py +218 -0
  123. floodmodeller_api/tool.py +332 -330
  124. floodmodeller_api/toolbox/__init__.py +5 -5
  125. floodmodeller_api/toolbox/example_tool.py +45 -45
  126. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  127. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -94
  128. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  129. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  130. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -72
  131. floodmodeller_api/units/__init__.py +10 -10
  132. floodmodeller_api/units/_base.py +214 -209
  133. floodmodeller_api/units/boundaries.py +467 -469
  134. floodmodeller_api/units/comment.py +52 -55
  135. floodmodeller_api/units/conduits.py +382 -403
  136. floodmodeller_api/units/helpers.py +123 -132
  137. floodmodeller_api/units/iic.py +107 -101
  138. floodmodeller_api/units/losses.py +305 -308
  139. floodmodeller_api/units/sections.py +444 -445
  140. floodmodeller_api/units/structures.py +1690 -1684
  141. floodmodeller_api/units/units.py +93 -102
  142. floodmodeller_api/units/unsupported.py +44 -44
  143. floodmodeller_api/units/variables.py +87 -89
  144. floodmodeller_api/urban1d/__init__.py +11 -11
  145. floodmodeller_api/urban1d/_base.py +188 -177
  146. floodmodeller_api/urban1d/conduits.py +93 -85
  147. floodmodeller_api/urban1d/general_parameters.py +58 -58
  148. floodmodeller_api/urban1d/junctions.py +81 -79
  149. floodmodeller_api/urban1d/losses.py +81 -74
  150. floodmodeller_api/urban1d/outfalls.py +114 -107
  151. floodmodeller_api/urban1d/raingauges.py +111 -108
  152. floodmodeller_api/urban1d/subsections.py +92 -93
  153. floodmodeller_api/urban1d/xsections.py +147 -141
  154. floodmodeller_api/util.py +77 -21
  155. floodmodeller_api/validation/parameters.py +660 -660
  156. floodmodeller_api/validation/urban_parameters.py +388 -404
  157. floodmodeller_api/validation/validation.py +110 -112
  158. floodmodeller_api/version.py +1 -1
  159. floodmodeller_api/xml2d.py +688 -684
  160. floodmodeller_api/xml2d_template.py +37 -37
  161. floodmodeller_api/zzn.py +387 -365
  162. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/LICENSE.txt +13 -13
  163. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/METADATA +82 -82
  164. floodmodeller_api-0.4.3.dist-info/RECORD +179 -0
  165. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/WHEEL +1 -1
  166. floodmodeller_api-0.4.3.dist-info/entry_points.txt +3 -0
  167. floodmodeller_api/libifcoremd.dll +0 -0
  168. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  169. floodmodeller_api/test/test_data/test_output.csv +0 -87
  170. floodmodeller_api/zzn_read.dll +0 -0
  171. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.bat +0 -2
  172. floodmodeller_api-0.4.2.data/scripts/fmapi-add_siltation.py +0 -3
  173. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.bat +0 -2
  174. floodmodeller_api-0.4.2.data/scripts/fmapi-structure_log.py +0 -3
  175. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.bat +0 -2
  176. floodmodeller_api-0.4.2.data/scripts/fmapi-toolbox.py +0 -41
  177. floodmodeller_api-0.4.2.dist-info/RECORD +0 -169
  178. {floodmodeller_api-0.4.2.dist-info → floodmodeller_api-0.4.3.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,8 @@
1
- from .dat import DAT
2
- from .ied import IED
3
- from .ief import IEF
4
- from .inp import INP
5
- from .logs import LF1, LF2
6
- from .util import open_docs
7
- from .version import __version__
8
- from .xml2d import XML2D
9
- from .zzn import ZZN
1
+ from .dat import DAT
2
+ from .ied import IED
3
+ from .ief import IEF
4
+ from .inp import INP
5
+ from .logs import LF1, LF2
6
+ from .version import __version__
7
+ from .xml2d import XML2D
8
+ from .zzn import ZZN
@@ -1,176 +1,184 @@
1
- """
2
- Flood Modeller Python API
3
- Copyright (C) 2023 Jacobs U.K. Limited
4
-
5
- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
- as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
-
8
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
-
11
- You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
-
13
- If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
- address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
- """
16
-
17
- """ Holds the base file class for API file classes """
18
-
19
- from pathlib import Path
20
-
21
- from .backup import File
22
- from .diff import check_item_with_dataframe_equal
23
- from .units._base import Unit
24
- from .units.iic import IIC
25
- from .urban1d._base import UrbanSubsection, UrbanUnit
26
- from .version import __version__
27
-
28
-
29
- class FMFile:
30
- """Base class for all Flood Modeller File types"""
31
-
32
- _filetype = None
33
- _suffix = None
34
-
35
- def __init__(self):
36
- if self._filepath is not None:
37
- self._filepath = Path(self._filepath).resolve() # save filepath to class
38
- # Check if filepath valid
39
- # * Add check or fix for path lengths greater than DOS standard length of 260 characters
40
-
41
- if not self._filepath.suffix.lower() == self._suffix:
42
- raise TypeError(
43
- f"Given filepath does not point to a {self._filetype} file. Please point to the full path for a {self._filetype} file"
44
- )
45
- if not self._filepath.exists():
46
- raise FileNotFoundError(
47
- f"{self._filetype} file does not exist! If you are wanting to create a new {self._filetype}, initiate the class without a given "
48
- f"filepath to create a new blank {self._filetype} or point the filepath of an existing {self._filetype} to use as a template, "
49
- f"then use the .save() method to save to a new filepath"
50
- )
51
- # If the file is not a ZZN file, then perform a backup
52
- # This performs a conditional back up, only copying the file if an equivalent copy doesn't already exist
53
- if not self._filetype == "ZZN":
54
- file = File(path=self._filepath)
55
- file.backup()
56
- # Add the file object as a property to expose the restore method
57
- self.file = file
58
-
59
- def __repr__(self):
60
- return f"<floodmodeller_api Class: {self._filetype}(filepath={self._filepath})>"
61
-
62
- def _write(self):
63
- raise NotImplementedError
64
-
65
- def _read(self):
66
- raise NotImplementedError
67
-
68
- def _update(self):
69
- f"""Updates the existing {self._filetype} based on any altered attributes"""
70
- if self._filepath is None:
71
- raise UserWarning(
72
- f"{self._filetype} must be saved to a specific filepath before update() can be called."
73
- )
74
-
75
- string = self._write()
76
- with open(self._filepath, "w") as _file:
77
- _file.write(string)
78
- print(f"{self._filetype} File Updated!")
79
-
80
- def _save(self, filepath):
81
- filepath = Path(filepath).absolute()
82
- if not filepath.suffix.lower() == self._suffix:
83
- raise TypeError(
84
- f'Given filepath does not point to a filepath suffixed "{self._suffix}". Please point to the full path to save the {self._filetype} file'
85
- )
86
-
87
- if not filepath.parent.exists():
88
- Path.mkdir(filepath.parent)
89
-
90
- string = self._write()
91
- with open(filepath, "w") as _file:
92
- _file.write(string)
93
- self._filepath = filepath # Updates the filepath attribute to the given path
94
-
95
- print(f"{self._filetype} File Saved to: {filepath}")
96
-
97
- def _diff(self, other, force_print=False):
98
- try:
99
- if self._filetype != other._filetype:
100
- raise TypeError("Cannot compare objects of different filetypes")
101
- diff = self._get_diff(other)
102
- if diff[0]:
103
- print("No difference, files are equivalent")
104
- else:
105
- print(f"Files not equivalent, {len(diff[1])} difference(s) found:")
106
- if len(diff[1]) > 25 and not force_print:
107
- print("[Showing first 25 differences...] ")
108
- print("\n".join([f" {name}: {reason}" for name, reason in diff[1][:25]]))
109
- print("\n...To see full list of all differences add force_print=True")
110
- else:
111
- print("\n".join([f" {name}: {reason}" for name, reason in diff[1]]))
112
- except Exception as e:
113
- self._handle_exception(e, when="compare")
114
-
115
- def _get_diff(self, other):
116
- return self.__eq__(other, return_diff=True)
117
-
118
- def _handle_exception(self, err, when):
119
- tb = err.__traceback__
120
- while tb.tb_next is not None:
121
- tb = tb.tb_next
122
- line_no = tb.tb_lineno
123
- tb_path = Path(tb.tb_frame.f_code.co_filename)
124
- fname = "/".join(tb_path.parts[-2:])
125
-
126
- raise Exception(
127
- "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
128
- f"\nAPI Error: Problem encountered when trying to {when} {self._filetype} file {self._filepath}."
129
- f"\n\nDetails: {__version__}-{fname}-{line_no}"
130
- f"\nMsg: {err}"
131
- "\n\nFor additional support, go to: https://github.com/People-Places-Solutions/floodmodeller-api"
132
- )
133
-
134
- def __eq__(self, other, return_diff=False):
135
- result = True
136
- diff = []
137
- try:
138
- for key, item in self.__dict__.items():
139
- try:
140
- if key in (
141
- "_filepath",
142
- "_raw_data",
143
- "_gxy_filepath",
144
- "_gxy_data",
145
- "_xmltree",
146
- "_xsd",
147
- "_xsdschema",
148
- "file",
149
- "_log_path",
150
- ):
151
- continue
152
- else:
153
- _result, diff = check_item_with_dataframe_equal(
154
- item,
155
- other.__dict__[key],
156
- name=f"{self._filetype}->{key}",
157
- diff=diff,
158
- special_types=(Unit, IIC, UrbanUnit, UrbanSubsection),
159
- )
160
- if not _result:
161
- result = False
162
- except KeyError as ke:
163
- result = False
164
- diff.append(
165
- (
166
- f"{self._filetype}->{key}",
167
- f"Key: '{ke.args[0]}' missing in other",
168
- )
169
- )
170
- continue
171
-
172
- except Exception as e:
173
- result = False
174
- diff.append((f"{self._filetype}->{key}", f"Error encountered: {e.args[0]}"))
175
-
176
- return (result, diff) if return_diff else result
1
+ """
2
+ Flood Modeller Python API
3
+ Copyright (C) 2024 Jacobs U.K. Limited
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+
11
+ You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
+
13
+ If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
+ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ """ Holds the base file class for API file classes """
20
+
21
+ from pathlib import Path
22
+ from typing import NoReturn
23
+
24
+ from .backup import File
25
+ from .diff import check_item_with_dataframe_equal
26
+ from .to_from_json import Jsonable
27
+ from .units._base import Unit
28
+ from .units.iic import IIC
29
+ from .urban1d._base import UrbanSubsection, UrbanUnit
30
+ from .version import __version__
31
+
32
+
33
+ class FMFile(Jsonable):
34
+ """Base class for all Flood Modeller File types"""
35
+
36
+ _filetype: str | None = None
37
+ _suffix: str | None = None
38
+ MAX_DIFF = 25
39
+
40
+ def __init__(self, filepath: str | Path | None = None, **kwargs):
41
+ if filepath is not None:
42
+ self._filepath = Path(filepath).resolve() # save filepath to class
43
+ # Check if filepath valid
44
+ # * Add check or fix for path lengths greater than DOS standard length of 260 characters
45
+
46
+ if not self._filepath.suffix.lower() == self._suffix:
47
+ raise TypeError(
48
+ f"Given filepath does not point to a {self._filetype} file. Please point to the full path for a {self._filetype} file",
49
+ )
50
+ if not self._filepath.exists():
51
+ raise FileNotFoundError(
52
+ f"{self._filetype} file does not exist! If you are wanting to create a new {self._filetype}, initiate the class without a given "
53
+ f"filepath to create a new blank {self._filetype} or point the filepath of an existing {self._filetype} to use as a template, "
54
+ f"then use the .save() method to save to a new filepath",
55
+ )
56
+ # If the file is not a ZZN file, then perform a backup
57
+ # This performs a conditional back up, only copying the file if an equivalent copy doesn't already exist
58
+ if self._filetype != "ZZN":
59
+ file = File(path=self._filepath)
60
+ file.backup()
61
+ # Add the file object as a property to expose the restore method
62
+ self.file = file
63
+
64
+ def __repr__(self):
65
+ return f"<floodmodeller_api Class: {self._filetype}(filepath={self._filepath})>"
66
+
67
+ def _write(self):
68
+ raise NotImplementedError
69
+
70
+ def _read(self):
71
+ raise NotImplementedError
72
+
73
+ def _update(self):
74
+ """Updates the existing self._filetype based on any altered attributes"""
75
+ if self._filepath is None:
76
+ raise UserWarning(
77
+ f"{self._filetype} must be saved to a specific filepath before update() can be called.",
78
+ )
79
+
80
+ string = self._write()
81
+ with open(self._filepath, "w") as _file:
82
+ _file.write(string)
83
+ print(f"{self._filetype} File Updated!")
84
+
85
+ def _save(self, filepath):
86
+ filepath = Path(filepath).absolute()
87
+ if not filepath.suffix.lower() == self._suffix:
88
+ raise TypeError(
89
+ f'Given filepath does not point to a filepath suffixed "{self._suffix}". Please point to the full path to save the {self._filetype} file',
90
+ )
91
+
92
+ if not filepath.parent.exists():
93
+ Path.mkdir(filepath.parent)
94
+
95
+ string = self._write()
96
+ with open(filepath, "w") as _file:
97
+ _file.write(string)
98
+ self._filepath = filepath # Updates the filepath attribute to the given path
99
+
100
+ print(f"{self._filetype} File Saved to: {filepath}")
101
+
102
+ def _diff(self, other, force_print=False):
103
+ try:
104
+ if self._filetype != other._filetype:
105
+ raise TypeError("Cannot compare objects of different filetypes")
106
+ diff = self._get_diff(other)
107
+ if diff[0]:
108
+ print("No difference, files are equivalent")
109
+ else:
110
+ print(f"Files not equivalent, {len(diff[1])} difference(s) found:")
111
+ if len(diff[1]) > self.MAX_DIFF and not force_print:
112
+ print(f"[Showing first {self.MAX_DIFF} differences...] ")
113
+ print(
114
+ "\n".join(
115
+ [f" {name}: {reason}" for name, reason in diff[1][: self.MAX_DIFF]],
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]]))
121
+ except Exception as e:
122
+ self._handle_exception(e, when="compare")
123
+
124
+ def _get_diff(self, other):
125
+ return self.__eq__(other, return_diff=True) # pylint: disable=unnecessary-dunder-call
126
+
127
+ def _handle_exception(self, err, when) -> NoReturn:
128
+ tb = err.__traceback__
129
+ while tb.tb_next is not None:
130
+ tb = tb.tb_next
131
+ line_no = tb.tb_lineno
132
+ tb_path = Path(tb.tb_frame.f_code.co_filename)
133
+ fname = "/".join(tb_path.parts[-2:])
134
+
135
+ raise Exception(
136
+ "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
137
+ f"\nAPI Error: Problem encountered when trying to {when} {self._filetype} file {self._filepath}."
138
+ f"\n\nDetails: {__version__}-{fname}-{line_no}"
139
+ f"\nMsg: {err}"
140
+ "\n\nFor additional support, go to: https://github.com/People-Places-Solutions/floodmodeller-api",
141
+ )
142
+
143
+ def __eq__(self, other, return_diff=False):
144
+ result = True
145
+ diff = []
146
+ try:
147
+ for key, item in self.__dict__.items():
148
+ try:
149
+ if key in (
150
+ "_filepath",
151
+ "_raw_data",
152
+ "_gxy_filepath",
153
+ "_gxy_data",
154
+ "_xmltree",
155
+ "_xsd",
156
+ "_xsdschema",
157
+ "file",
158
+ "_log_path",
159
+ ):
160
+ continue
161
+ _result, diff = check_item_with_dataframe_equal(
162
+ item,
163
+ other.__dict__[key],
164
+ name=f"{self._filetype}->{key}",
165
+ diff=diff,
166
+ special_types=(Unit, IIC, UrbanUnit, UrbanSubsection),
167
+ )
168
+ if not _result:
169
+ result = False
170
+ except KeyError as ke:
171
+ result = False
172
+ diff.append(
173
+ (
174
+ f"{self._filetype}->{key}",
175
+ f"Key: '{ke.args[0]}' missing in other",
176
+ ),
177
+ )
178
+ continue
179
+
180
+ except Exception as e:
181
+ result = False
182
+ diff.append((f"{self._filetype}->{key}", f"Error encountered: {e.args[0]}"))
183
+
184
+ return (result, diff) if return_diff else result