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.
Files changed (119) hide show
  1. floodmodeller_api/__init__.py +11 -1
  2. floodmodeller_api/_base.py +55 -36
  3. floodmodeller_api/backup.py +15 -12
  4. floodmodeller_api/dat.py +191 -121
  5. floodmodeller_api/diff.py +4 -4
  6. floodmodeller_api/hydrology_plus/hydrology_plus_export.py +15 -14
  7. floodmodeller_api/ied.py +8 -10
  8. floodmodeller_api/ief.py +56 -42
  9. floodmodeller_api/ief_flags.py +1 -1
  10. floodmodeller_api/inp.py +7 -10
  11. floodmodeller_api/logs/lf.py +25 -26
  12. floodmodeller_api/logs/lf_helpers.py +20 -20
  13. floodmodeller_api/logs/lf_params.py +1 -5
  14. floodmodeller_api/mapping.py +11 -2
  15. floodmodeller_api/test/__init__.py +2 -2
  16. floodmodeller_api/test/conftest.py +2 -3
  17. floodmodeller_api/test/test_backup.py +2 -2
  18. floodmodeller_api/test/test_conveyance.py +13 -7
  19. floodmodeller_api/test/test_dat.py +168 -20
  20. floodmodeller_api/test/test_data/EX18_DAT_expected.json +164 -144
  21. floodmodeller_api/test/test_data/EX3_DAT_expected.json +6 -2
  22. floodmodeller_api/test/test_data/EX6_DAT_expected.json +12 -46
  23. floodmodeller_api/test/test_data/encoding_test_cp1252.dat +1081 -0
  24. floodmodeller_api/test/test_data/encoding_test_utf8.dat +1081 -0
  25. floodmodeller_api/test/test_data/integrated_bridge/AR_NoSP_NoBl_2O_NO_OneFRC.ied +33 -0
  26. floodmodeller_api/test/test_data/integrated_bridge/AR_vSP_25pc_1O.ied +32 -0
  27. floodmodeller_api/test/test_data/integrated_bridge/PL_vSP_25pc_1O.ied +34 -0
  28. floodmodeller_api/test/test_data/integrated_bridge/SBTwoFRCsStaggered.IED +32 -0
  29. floodmodeller_api/test/test_data/integrated_bridge/US_NoSP_NoBl_OR_RN.ied +28 -0
  30. floodmodeller_api/test/test_data/integrated_bridge/US_SP_NoBl_OR_frc_PT2-5_RN.ied +34 -0
  31. floodmodeller_api/test/test_data/integrated_bridge/US_fSP_NoBl_1O.ied +30 -0
  32. floodmodeller_api/test/test_data/integrated_bridge/US_nSP_NoBl_1O.ied +49 -0
  33. floodmodeller_api/test/test_data/integrated_bridge/US_vSP_NoBl_2O_Para.ied +35 -0
  34. floodmodeller_api/test/test_data/integrated_bridge.dat +40 -0
  35. floodmodeller_api/test/test_data/network.ied +2 -2
  36. floodmodeller_api/test/test_data/network_dat_expected.json +141 -243
  37. floodmodeller_api/test/test_data/network_ied_expected.json +2 -2
  38. floodmodeller_api/test/test_data/network_with_comments.ied +2 -2
  39. floodmodeller_api/test/test_data/structure_logs/EX17_expected.csv +4 -0
  40. floodmodeller_api/test/test_data/structure_logs/EX17_expected.json +69 -0
  41. floodmodeller_api/test/test_data/structure_logs/EX18_expected.csv +20 -0
  42. floodmodeller_api/test/test_data/structure_logs/EX18_expected.json +292 -0
  43. floodmodeller_api/test/test_data/structure_logs/EX6_expected.csv +4 -0
  44. floodmodeller_api/test/test_data/structure_logs/EX6_expected.json +35 -0
  45. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_flow.csv +182 -0
  46. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_fr.csv +182 -0
  47. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_mode.csv +182 -0
  48. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_stage.csv +182 -0
  49. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_state.csv +182 -0
  50. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_velocity.csv +182 -0
  51. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_h.csv +182 -0
  52. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_mode.csv +182 -0
  53. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_link_inflow.csv +182 -0
  54. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_max.csv +87 -0
  55. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_h.csv +182 -0
  56. floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_mode.csv +182 -0
  57. floodmodeller_api/test/test_flowtimeprofile.py +2 -2
  58. floodmodeller_api/test/test_hydrology_plus_export.py +4 -2
  59. floodmodeller_api/test/test_ied.py +3 -3
  60. floodmodeller_api/test/test_ief.py +12 -4
  61. floodmodeller_api/test/test_inp.py +2 -2
  62. floodmodeller_api/test/test_integrated_bridge.py +159 -0
  63. floodmodeller_api/test/test_json.py +14 -13
  64. floodmodeller_api/test/test_logs_lf.py +50 -29
  65. floodmodeller_api/test/test_read_file.py +1 -0
  66. floodmodeller_api/test/test_river.py +12 -12
  67. floodmodeller_api/test/test_tool.py +8 -5
  68. floodmodeller_api/test/test_toolbox_structure_log.py +148 -158
  69. floodmodeller_api/test/test_xml2d.py +14 -16
  70. floodmodeller_api/test/test_zz.py +143 -0
  71. floodmodeller_api/to_from_json.py +9 -9
  72. floodmodeller_api/tool.py +15 -11
  73. floodmodeller_api/toolbox/example_tool.py +5 -1
  74. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +13 -9
  75. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +500 -194
  76. floodmodeller_api/toolbox/model_build/structure_log_definition.py +5 -1
  77. floodmodeller_api/units/__init__.py +15 -0
  78. floodmodeller_api/units/_base.py +87 -20
  79. floodmodeller_api/units/_helpers.py +343 -0
  80. floodmodeller_api/units/boundaries.py +59 -71
  81. floodmodeller_api/units/comment.py +1 -1
  82. floodmodeller_api/units/conduits.py +57 -54
  83. floodmodeller_api/units/connectors.py +112 -0
  84. floodmodeller_api/units/controls.py +107 -0
  85. floodmodeller_api/units/conveyance.py +1 -1
  86. floodmodeller_api/units/iic.py +2 -9
  87. floodmodeller_api/units/losses.py +44 -45
  88. floodmodeller_api/units/sections.py +52 -51
  89. floodmodeller_api/units/structures.py +361 -531
  90. floodmodeller_api/units/units.py +27 -26
  91. floodmodeller_api/units/unsupported.py +5 -7
  92. floodmodeller_api/units/variables.py +2 -2
  93. floodmodeller_api/urban1d/_base.py +13 -17
  94. floodmodeller_api/urban1d/conduits.py +11 -21
  95. floodmodeller_api/urban1d/general_parameters.py +1 -1
  96. floodmodeller_api/urban1d/junctions.py +7 -11
  97. floodmodeller_api/urban1d/losses.py +13 -17
  98. floodmodeller_api/urban1d/outfalls.py +18 -22
  99. floodmodeller_api/urban1d/raingauges.py +5 -10
  100. floodmodeller_api/urban1d/subsections.py +5 -4
  101. floodmodeller_api/urban1d/xsections.py +14 -17
  102. floodmodeller_api/util.py +23 -6
  103. floodmodeller_api/validation/parameters.py +7 -3
  104. floodmodeller_api/validation/urban_parameters.py +1 -4
  105. floodmodeller_api/validation/validation.py +11 -5
  106. floodmodeller_api/version.py +1 -1
  107. floodmodeller_api/xml2d.py +27 -31
  108. floodmodeller_api/xml2d_template.py +1 -1
  109. floodmodeller_api/zz.py +539 -0
  110. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/LICENSE.txt +1 -1
  111. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/METADATA +30 -16
  112. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/RECORD +116 -83
  113. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/WHEEL +1 -1
  114. floodmodeller_api/test/test_zzn.py +0 -36
  115. floodmodeller_api/units/helpers.py +0 -123
  116. floodmodeller_api/zzn.py +0 -414
  117. /floodmodeller_api/test/test_data/{network_from_tabularCSV.csv → tabular_csv_outputs/network_zzn_max.csv} +0 -0
  118. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/entry_points.txt +0 -0
  119. {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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.
@@ -14,7 +14,7 @@ If you have any query about this program or this License, please contact us at s
14
14
  address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
15
  """
16
16
 
17
- from floodmodeller_api.units.helpers import _to_float, _to_int, join_n_char_ljust
17
+ from floodmodeller_api.units._helpers import join_n_char_ljust, to_float, to_int
18
18
  from floodmodeller_api.validation import _validate_unit
19
19
 
20
20
  from ._base import UrbanSubsection, UrbanUnit
@@ -31,13 +31,13 @@ class XSECTION(UrbanUnit):
31
31
  geom3 (float): auxiliary parameter (width, side slopes, etc.) as listed in Table D-1. (required, applicable to shape types)
32
32
  geom4 (float):auxiliary parameter (width, side slopes, etc.) as listed in Table D-1. (required, applicable to shape types)
33
33
  barrels (float): Barrels type number of barrels (i.e., number of parallel pipes of equal size, slope, and roughness) associated with a conduit of shape type , or "CUSTOM" type (optional, default is 1).
34
- culvert (int): Culvert code number from Table A.10 for the conduits inlet geometry if it is a culvert subject to possible inlet flow control. Only an option for shape type (leave blank otherwise) (optional, default is "").
34
+ culvert (int): Culvert code number from Table A.10 for the conduit's inlet geometry if it is a culvert subject to possible inlet flow control. Only an option for shape type (leave blank otherwise) (optional, default is "").
35
35
  curve (str): Curve name of a Shape Curve in the [CURVES] section that defines how width varies with depth. (optional, applicable to shape types only)
36
36
  tsect (str): Name of an entry in the [TRANSECTS] section that describes the cross-section geometry of an irregular channel. (required, applicable to "IREGUALAR types only)
37
37
 
38
38
 
39
39
  Returns:
40
- XSECTION: Flood Modeller XSECTION Unit class object TODO: add urban 1d in to all instances within urban 1d API
40
+ XSECTION: Flood Modeller XSECTION Unit class object
41
41
  """
42
42
 
43
43
  _unit = "XSECTION"
@@ -48,8 +48,6 @@ class XSECTION(UrbanUnit):
48
48
  def _read(self, line):
49
49
  unit_data = line.split()
50
50
 
51
- # TODO: add functionality to read comments
52
- # TODO: consider appropriate defaults
53
51
  self.name = str(unit_data[0])
54
52
 
55
53
  if unit_data[1] in _shape_options:
@@ -59,20 +57,20 @@ class XSECTION(UrbanUnit):
59
57
  unit_data.append("")
60
58
 
61
59
  self.shape = str(unit_data[1])
62
- self.geom1 = _to_float(unit_data[2], 0.0)
63
- self.geom2 = _to_float(unit_data[3], 0.0)
64
- self.geom3 = _to_float(unit_data[4], 0.0)
65
- self.geom4 = _to_float(unit_data[5], 0.0)
66
- self.barrels = _to_int(unit_data[6], 1)
67
- self.culvert = _to_int(unit_data[7], "")
60
+ self.geom1 = to_float(unit_data[2], 0.0)
61
+ self.geom2 = to_float(unit_data[3], 0.0)
62
+ self.geom3 = to_float(unit_data[4], 0.0)
63
+ self.geom4 = to_float(unit_data[5], 0.0)
64
+ self.barrels = to_int(unit_data[6], 1)
65
+ self.culvert = to_int(unit_data[7], "")
68
66
 
69
67
  elif unit_data[1] == "CUSTOM":
70
68
  while len(unit_data) < self.MIN_LENGTH_CUSTOM:
71
69
  unit_data.append("")
72
70
 
73
71
  self.shape = str(unit_data[1])
74
- self.geom1 = _to_float(unit_data[2], "")
75
- self.barrels = _to_int(unit_data[6], 1)
72
+ self.geom1 = to_float(unit_data[2], "")
73
+ self.barrels = to_int(unit_data[6], 1)
76
74
 
77
75
  elif unit_data[1] == "IRREGULAR":
78
76
  while len(unit_data) < self.MIN_LENGTH_IRREGULAR:
@@ -86,8 +84,6 @@ class XSECTION(UrbanUnit):
86
84
 
87
85
  _validate_unit(self, urban=True)
88
86
 
89
- # TODO:Improve indentation format when writing and include header for completeness
90
-
91
87
  params1 = join_n_char_ljust(17, self.name)
92
88
 
93
89
  if self.shape in _shape_options:
@@ -109,7 +105,8 @@ class XSECTION(UrbanUnit):
109
105
  params2 = join_n_char_ljust(15, self.shape, self.tsect)
110
106
 
111
107
  else:
112
- raise RuntimeError(f"{self.shape} not supported")
108
+ msg = f"{self.shape} not supported"
109
+ raise RuntimeError(msg)
113
110
 
114
111
  return params1 + params2
115
112
 
floodmodeller_api/util.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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,7 +18,7 @@ from __future__ import annotations
18
18
 
19
19
  import sys
20
20
  import webbrowser
21
- from functools import wraps
21
+ from functools import cache, wraps
22
22
  from pathlib import Path
23
23
  from typing import TYPE_CHECKING
24
24
 
@@ -58,7 +58,7 @@ def read_file(filepath: str | Path) -> FMFile:
58
58
 
59
59
 
60
60
  """
61
- from . import DAT, IED, IEF, INP, LF1, LF2, XML2D, ZZN
61
+ from . import DAT, IED, IEF, INP, LF1, LF2, XML2D, ZZN, ZZX
62
62
  from .hydrology_plus import HydrologyPlusExport
63
63
 
64
64
  suffix_to_class = {
@@ -67,6 +67,7 @@ def read_file(filepath: str | Path) -> FMFile:
67
67
  ".ied": IED,
68
68
  ".xml": XML2D,
69
69
  ".zzn": ZZN,
70
+ ".zzx": ZZX,
70
71
  ".inp": INP,
71
72
  ".lf1": LF1,
72
73
  ".lf2": LF2,
@@ -77,13 +78,27 @@ def read_file(filepath: str | Path) -> FMFile:
77
78
  if api_class:
78
79
  return api_class(filepath)
79
80
 
80
- raise ValueError(f"Unsupported file type: {filepath.suffix}")
81
+ msg = f"Unsupported file type: {filepath.suffix}"
82
+ raise ValueError(msg)
81
83
 
82
84
 
85
+ @cache
83
86
  def is_windows() -> bool:
84
87
  return sys.platform.startswith("win")
85
88
 
86
89
 
90
+ def get_associated_file(original_file: Path, new_suffix: str) -> Path:
91
+ new_file = original_file.with_suffix(new_suffix)
92
+ if not new_file.exists():
93
+ msg = (
94
+ f"Error: Could not find associated {new_suffix} file."
95
+ f" Ensure that the {original_file.suffix} results"
96
+ f" have an associated {new_suffix} file with matching name."
97
+ )
98
+ raise FileNotFoundError(msg)
99
+ return new_file
100
+
101
+
87
102
  def handle_exception(when: str) -> Callable:
88
103
  """Decorator factory to wrap a method with exception handling."""
89
104
 
@@ -103,7 +118,7 @@ def handle_exception(when: str) -> Callable:
103
118
  class FloodModellerAPIError(Exception):
104
119
  """Custom exception class for Flood Modeller API errors."""
105
120
 
106
- def __init__(self, original_exception, when, filetype, filepath) -> None:
121
+ def __init__(self, original_exception, when, filetype, filepath: Path | None = None) -> None:
107
122
  tb = original_exception.__traceback__
108
123
  while tb.tb_next is not None:
109
124
  tb = tb.tb_next
@@ -111,9 +126,11 @@ class FloodModellerAPIError(Exception):
111
126
  tb_path = Path(tb.tb_frame.f_code.co_filename)
112
127
  fname = "/".join(tb_path.parts[-2:])
113
128
 
129
+ end_of_str = f"{filepath}" if filepath is not None else "from blank"
130
+
114
131
  message = (
115
132
  "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
116
- f"\nAPI Error: Problem encountered when trying to {when} {filetype} file {filepath}."
133
+ f"\nAPI Error: Problem encountered when trying to {when} {filetype} file {end_of_str}."
117
134
  f"\n\nDetails: {__version__}-{fname}-{line_no}"
118
135
  f"\nMsg: {original_exception}"
119
136
  "\n\nFor additional support, go to: https://github.com/People-Places-Solutions/floodmodeller-api"
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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.
@@ -201,10 +201,10 @@ parameter_options = {
201
201
  "type": "value-match",
202
202
  "options": ["RECTANGLE", "CYLINDER", "SQUARE", "I-BEAM"],
203
203
  },
204
- # Pier_faces is an optional parameters
204
+ # Pier_faces is an optional parameter
205
205
  "pier_faces": {
206
206
  "type": "value-match",
207
- "options": ["", "STREAMLINE", "SEMICIRCLE", "TRIANGLE", "DIAPHRAGM"],
207
+ "options": ["", "STREAMLINE", "SEMICIRCLE", "TRIANGLE", "DIAPHRAGM", "DIA"],
208
208
  },
209
209
  "soffit_shape": {
210
210
  "type": "value-match",
@@ -657,4 +657,8 @@ parameter_options = {
657
657
  "type": "type-match",
658
658
  "options": (float, int),
659
659
  },
660
+ "labels": {
661
+ "type": "list-string-length",
662
+ "max_length": 12,
663
+ },
660
664
  }
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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.
@@ -44,9 +44,7 @@ urban_parameter_options = {
44
44
  "options": ["INTENSITY", "VOLUME", "CUMULATIVE"],
45
45
  },
46
46
  "interval": {
47
- # TODO: UPDATE TO CONSIDER - decimal hours or hours:minutes format (e.g., 0:15 for 15-minute readings). search for presence of ";" during _read maybe?
48
47
  # try turing to float else keep as string
49
- # TODO: add new a type of match called RegEx match for example "[0-9]:[0-9]",
50
48
  "type": "type-match",
51
49
  "options": (float, int, str),
52
50
  },
@@ -200,7 +198,6 @@ urban_parameter_options = {
200
198
  "options": (type(None), int),
201
199
  },
202
200
  "culvert": {
203
- # TODO: str added to allow case when optional parameter not provided (and so is ""). Update to improve handling,
204
201
  "type": "type-match",
205
202
  "options": (type(None), int, str),
206
203
  },
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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.
@@ -23,9 +23,8 @@ def _validate_unit(unit, urban=False):
23
23
  param_validation_dict = {}
24
24
  all_valid = True
25
25
  for param in dir(unit):
26
- options = (
27
- parameter_options if not urban else urban_parameter_options
28
- ) # define which dictionary to use
26
+ # define which dictionary to use
27
+ options = parameter_options if not urban else urban_parameter_options
29
28
 
30
29
  if param in options:
31
30
  value = getattr(unit, param)
@@ -41,7 +40,8 @@ def _validate_unit(unit, urban=False):
41
40
  if not value[0]
42
41
  ],
43
42
  )
44
- raise ValueError(f"One or more parameters in {repr(unit)} are invalid:\n {errors}")
43
+ msg = f"One or more parameters in {unit!r} are invalid:\n {errors}"
44
+ raise ValueError(msg)
45
45
 
46
46
 
47
47
  def _validate_parameter(param, value): # noqa: C901, PLR0911, PLR0912
@@ -87,6 +87,12 @@ def _validate_parameter(param, value): # noqa: C901, PLR0911, PLR0912
87
87
  f'-> Exceeds {param["max_length"]} characters',
88
88
  )
89
89
 
90
+ if param["type"] == "list-string-length":
91
+ return (
92
+ all(len(item) <= param["max_length"] for item in value),
93
+ f'-> Contains labels exceeding {param["max_length"]} characters',
94
+ )
95
+
90
96
  if param["type"] == "dict-match":
91
97
  for key, rule in param["options"].items():
92
98
  if key not in value:
@@ -1 +1 @@
1
- __version__ = "0.5.0.post1"
1
+ __version__ = "0.5.2"
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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,7 +17,7 @@ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London
17
17
  from __future__ import annotations
18
18
 
19
19
  import io
20
- import os
20
+ import logging
21
21
  import time
22
22
  from copy import deepcopy
23
23
  from pathlib import Path
@@ -205,12 +205,9 @@ class XML2D(FMFile):
205
205
 
206
206
  def _validate(self):
207
207
  try:
208
- self._xsdschema.assert_(self._xmltree)
208
+ self._xsdschema.assert_(self._xmltree) # noqa: PT009
209
209
  except AssertionError as err:
210
- msg = (
211
- f"XML Validation Error for {repr(self)}:\n"
212
- f" {err.args[0].replace(self._ns, '')}"
213
- )
210
+ msg = f"XML Validation Error for {self!r}:\n {err.args[0].replace(self._ns, '')}"
214
211
  raise ValueError(msg) from err
215
212
 
216
213
  def _recursive_update_xml( # noqa: C901, PLR0912
@@ -220,11 +217,10 @@ class XML2D(FMFile):
220
217
  parent_key,
221
218
  list_idx=None,
222
219
  ):
223
- # TODO: Handle removing params
224
-
225
220
  for key, item in new_dict.items():
226
221
  if key in self._multi_value_keys and not isinstance(item, list):
227
- raise Exception(f"Element: '{key}' must be added as list")
222
+ msg = f"Element: '{key}' must be added as list"
223
+ raise Exception(msg)
228
224
  if parent_key == "ROOT":
229
225
  parent = self._xmltree.getroot()
230
226
  else:
@@ -294,7 +290,8 @@ class XML2D(FMFile):
294
290
  from_list=False,
295
291
  ):
296
292
  if add_key in self._multi_value_keys and not isinstance(add_item, list) and not from_list:
297
- raise Exception(f"Element: '{add_key}' must be added as list")
293
+ msg = f"Element: '{add_key}' must be added as list"
294
+ raise Exception(msg)
298
295
  if isinstance(add_item, dict):
299
296
  new_element = etree.SubElement(parent, f"{self._ns}{add_key}")
300
297
  for key, item in add_item.items():
@@ -336,6 +333,10 @@ class XML2D(FMFile):
336
333
  f".//{self._w3_schema}*[@name='{add_key}']",
337
334
  )
338
335
 
336
+ if schema_elem is None:
337
+ msg = f"Schema element for key '{add_key}' not found in XSD."
338
+ raise ValueError(msg)
339
+
339
340
  if schema_elem.tag.endswith("attribute"):
340
341
  parent.set(add_key, str(add_item))
341
342
 
@@ -501,20 +502,15 @@ class XML2D(FMFile):
501
502
 
502
503
  """
503
504
 
504
- # TODO:
505
- # - Clean up the lf code?
506
- # - Remove or sort out get results
507
-
508
505
  self.range_function = range_function
509
506
  self.range_settings = range_settings if range_settings else {}
510
507
 
511
508
  if self._filepath is None:
512
- raise UserWarning(
513
- "xml2D must be saved to a specific filepath before simulate() can be called.",
514
- )
509
+ msg = "xml2D must be saved to a specific filepath before simulate() can be called."
510
+ raise UserWarning(msg)
515
511
  if precision.upper() == "DEFAULT":
516
512
  precision = "SINGLE" # defaults to single precision
517
- for _, domain in self.domains.items():
513
+ for domain in self.domains.values():
518
514
  if domain["run_data"].get("double_precision") == "required":
519
515
  precision = "DOUBLE"
520
516
  break
@@ -525,14 +521,12 @@ class XML2D(FMFile):
525
521
  else:
526
522
  _enginespath = enginespath
527
523
  if not Path(_enginespath).exists():
528
- raise Exception(
529
- f"Flood Modeller non-default engine path not found! {str(_enginespath)}",
530
- )
524
+ msg = f"Flood Modeller non-default engine path not found! {_enginespath!s}"
525
+ raise Exception(msg)
531
526
 
532
527
  # checking if all schemes used are fast, if so will use FAST.exe
533
- # TODO: Add in option to choose to use or not to use if you can
534
528
  is_fast = True
535
- for _, domain in self.domains.items():
529
+ for domain in self.domains.values():
536
530
  if domain["run_data"]["scheme"] != "FAST":
537
531
  is_fast = False
538
532
  break
@@ -545,7 +539,8 @@ class XML2D(FMFile):
545
539
  isis2d_fp = str(Path(_enginespath, "ISIS2d_DP.exe"))
546
540
 
547
541
  if not Path(isis2d_fp).exists():
548
- raise Exception(f"Flood Modeller engine not found! Expected location: {isis2d_fp}")
542
+ msg = f"Flood Modeller engine not found! Expected location: {isis2d_fp}"
543
+ raise Exception(msg)
549
544
 
550
545
  console_output = console_output.lower()
551
546
  run_command = (
@@ -554,9 +549,9 @@ class XML2D(FMFile):
554
549
  stdout = DEVNULL if console_output == "simple" else None
555
550
 
556
551
  if method.upper() == "WAIT":
557
- print("Executing simulation ... ")
552
+ logging.info("Executing simulation ... ")
558
553
  # execute simulation
559
- process = Popen(run_command, cwd=os.path.dirname(self._filepath), stdout=stdout)
554
+ process = Popen(run_command, cwd=Path(self._filepath).parent, stdout=stdout)
560
555
 
561
556
  # progress bar based on log files:
562
557
  if console_output == "simple":
@@ -571,9 +566,9 @@ class XML2D(FMFile):
571
566
  self._interpret_exit_code(exitcode, raise_on_failure)
572
567
 
573
568
  elif method.upper() == "RETURN_PROCESS":
574
- print("Executing simulation ...")
569
+ logging.info("Executing simulation ...")
575
570
  # execute simulation
576
- return Popen(run_command, cwd=os.path.dirname(self._filepath), stdout=stdout)
571
+ return Popen(run_command, cwd=Path(self._filepath).parent, stdout=stdout)
577
572
 
578
573
  return None
579
574
 
@@ -584,7 +579,8 @@ class XML2D(FMFile):
584
579
  floodmodeller_api.LF2 class object
585
580
  """
586
581
  if not self._log_path.exists():
587
- raise FileNotFoundError("Log file (LF2) not found")
582
+ msg = "Log file (LF2) not found"
583
+ raise FileNotFoundError(msg)
588
584
 
589
585
  return LF2(self._log_path)
590
586
 
@@ -636,4 +632,4 @@ class XML2D(FMFile):
636
632
 
637
633
  if raise_on_failure and exitcode != self.GOOD_EXIT_CODE:
638
634
  raise Exception(msg)
639
- print(msg)
635
+ logging.info(msg)
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
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.