ras-commander 0.35.0__py3-none-any.whl → 0.37.0__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.
ras_commander/RasPlan.py CHANGED
@@ -1,3 +1,29 @@
1
+ """
2
+ RasPlan - Operations for handling plan files in HEC-RAS projects
3
+
4
+ This module is part of the ras-commander library and uses a centralized logging configuration.
5
+
6
+ Logging Configuration:
7
+ - The logging is set up in the logging_config.py file.
8
+ - A @log_call decorator is available to automatically log function calls.
9
+ - Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
10
+ - Logs are written to both console and a rotating file handler.
11
+ - The default log file is 'ras_commander.log' in the 'logs' directory.
12
+ - The default log level is INFO.
13
+
14
+ To use logging in this module:
15
+ 1. Use the @log_call decorator for automatic function call logging.
16
+ 2. For additional logging, use logger.[level]() calls (e.g., logger.info(), logger.debug()).
17
+ 3. Obtain the logger using: logger = logging.getLogger(__name__)
18
+
19
+ Example:
20
+ @log_call
21
+ def my_function():
22
+ logger = logging.getLogger(__name__)
23
+ logger.debug("Additional debug information")
24
+ # Function logic here
25
+ """
26
+ import os
1
27
  import re
2
28
  import logging
3
29
  from pathlib import Path
@@ -6,28 +32,20 @@ from typing import Union, Optional
6
32
  import pandas as pd
7
33
  from .RasPrj import RasPrj, ras
8
34
  from .RasUtils import RasUtils
9
-
10
-
11
35
  from pathlib import Path
12
36
  from typing import Union, Any
13
37
  import logging
14
38
  import re
39
+ from ras_commander import get_logger
40
+ from ras_commander.logging_config import log_call
15
41
 
16
-
17
- # Configure logging
18
- logging.basicConfig(
19
- level=logging.INFO,
20
- format='%(asctime)s - %(levelname)s - %(message)s',
21
- handlers=[
22
- logging.StreamHandler()
23
- ]
24
- )
42
+ logger = get_logger(__name__)
25
43
 
26
44
  class RasPlan:
27
45
  """
28
46
  A class for operations on HEC-RAS plan files.
29
47
  """
30
-
48
+ @log_call
31
49
  @staticmethod
32
50
  def set_geom(plan_number: Union[str, int], new_geom: Union[str, int], ras_object=None) -> pd.DataFrame:
33
51
  """
@@ -110,6 +128,7 @@ class RasPlan:
110
128
  return ras_obj.plan_df
111
129
 
112
130
  @staticmethod
131
+ @log_call
113
132
  def set_steady(plan_number: str, new_steady_flow_number: str, ras_object=None):
114
133
  """
115
134
  Apply a steady flow file to a plan file.
@@ -132,7 +151,6 @@ class RasPlan:
132
151
  Note:
133
152
  This function updates the ras object's dataframes after modifying the project structure.
134
153
  """
135
- logging.info(f"Setting steady flow file to {new_steady_flow_number} in Plan {plan_number}")
136
154
  ras_obj = ras_object or ras
137
155
  ras_obj.check_initialized()
138
156
 
@@ -140,27 +158,23 @@ class RasPlan:
140
158
  ras_obj.flow_df = ras_obj.get_flow_entries()
141
159
 
142
160
  if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
143
- logging.error(f"Steady flow number {new_steady_flow_number} not found in project file.")
144
161
  raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
145
162
 
146
163
  # Resolve the full path of the plan file
147
164
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
148
165
  if not plan_file_path:
149
- logging.error(f"Plan file not found: {plan_number}")
150
166
  raise FileNotFoundError(f"Plan file not found: {plan_number}")
151
167
 
152
168
  try:
153
169
  with open(plan_file_path, 'r') as f:
154
170
  lines = f.readlines()
155
171
  except FileNotFoundError:
156
- logging.error(f"Plan file not found: {plan_file_path}")
157
- raise
172
+ raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
158
173
 
159
174
  with open(plan_file_path, 'w') as f:
160
175
  for line in lines:
161
176
  if line.startswith("Flow File=f"):
162
177
  f.write(f"Flow File=f{new_steady_flow_number}\n")
163
- logging.info(f"Updated Flow File in {plan_file_path} to f{new_steady_flow_number}")
164
178
  else:
165
179
  f.write(line)
166
180
 
@@ -171,6 +185,7 @@ class RasPlan:
171
185
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
172
186
 
173
187
  @staticmethod
188
+ @log_call
174
189
  def set_unsteady(plan_number: str, new_unsteady_flow_number: str, ras_object=None):
175
190
  """
176
191
  Apply an unsteady flow file to a plan file.
@@ -193,8 +208,6 @@ class RasPlan:
193
208
  Note:
194
209
  This function updates the ras object's dataframes after modifying the project structure.
195
210
  """
196
- logging.info(f"Setting unsteady flow file to {new_unsteady_flow_number} in Plan {plan_number}")
197
-
198
211
  ras_obj = ras_object or ras
199
212
  ras_obj.check_initialized()
200
213
 
@@ -202,21 +215,17 @@ class RasPlan:
202
215
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
203
216
 
204
217
  if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
205
- logging.error(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
206
218
  raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
207
219
 
208
220
  # Get the full path of the plan file
209
221
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
210
222
  if not plan_file_path:
211
- logging.error(f"Plan file not found: {plan_number}")
212
223
  raise FileNotFoundError(f"Plan file not found: {plan_number}")
213
224
 
214
225
  try:
215
226
  RasUtils.update_plan_file(plan_file_path, 'Unsteady', new_unsteady_flow_number)
216
- logging.info(f"Updated unsteady flow file in {plan_file_path} to u{new_unsteady_flow_number}")
217
227
  except Exception as e:
218
- logging.error(f"Failed to update unsteady flow file: {e}")
219
- raise
228
+ raise Exception(f"Failed to update unsteady flow file: {e}")
220
229
 
221
230
  # Update the ras object's dataframes
222
231
  ras_obj.plan_df = ras_obj.get_plan_entries()
@@ -225,6 +234,7 @@ class RasPlan:
225
234
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
226
235
 
227
236
  @staticmethod
237
+ @log_call
228
238
  def set_num_cores(plan_number, num_cores, ras_object=None):
229
239
  """
230
240
  Update the maximum number of cores to use in the HEC-RAS plan file.
@@ -253,8 +263,6 @@ class RasPlan:
253
263
  Note:
254
264
  This function updates the ras object's dataframes after modifying the project structure.
255
265
  """
256
- logging.info(f"Setting num_cores to {num_cores} in Plan {plan_number}")
257
-
258
266
  ras_obj = ras_object or ras
259
267
  ras_obj.check_initialized()
260
268
 
@@ -262,7 +270,6 @@ class RasPlan:
262
270
  if Path(plan_number).is_file():
263
271
  plan_file_path = Path(plan_number)
264
272
  if not plan_file_path.exists():
265
- logging.error(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
266
273
  raise FileNotFoundError(f"Plan file not found: {plan_file_path}. Please provide a valid plan number or path.")
267
274
  else:
268
275
  # Update the plan dataframe in the ras instance to ensure it is current
@@ -271,7 +278,6 @@ class RasPlan:
271
278
  # Get the full path of the plan file
272
279
  plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
273
280
  if not plan_file_path:
274
- logging.error(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
275
281
  raise FileNotFoundError(f"Plan file not found: {plan_number}. Please provide a valid plan number or path.")
276
282
 
277
283
  cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
@@ -279,17 +285,14 @@ class RasPlan:
279
285
  with open(plan_file_path, 'r') as file:
280
286
  content = file.read()
281
287
  except FileNotFoundError:
282
- logging.error(f"Plan file not found: {plan_file_path}")
283
- raise
288
+ raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
284
289
 
285
290
  new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
286
291
  try:
287
292
  with open(plan_file_path, 'w') as file:
288
293
  file.write(new_content)
289
- logging.info(f"Updated {plan_file_path} with {num_cores} cores.")
290
294
  except IOError as e:
291
- logging.error(f"Failed to write to plan file: {e}")
292
- raise
295
+ raise IOError(f"Failed to write to plan file: {e}")
293
296
 
294
297
  # Update the ras object's dataframes
295
298
  ras_obj.plan_df = ras_obj.get_plan_entries()
@@ -298,6 +301,7 @@ class RasPlan:
298
301
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
299
302
 
300
303
  @staticmethod
304
+ @log_call
301
305
  def set_geom_preprocessor(file_path, run_htab, use_ib_tables, ras_object=None):
302
306
  """
303
307
  Update the simulation plan file to modify the `Run HTab` and `UNET Use Existing IB Tables` settings.
@@ -330,37 +334,27 @@ class RasPlan:
330
334
  ras_obj.check_initialized()
331
335
 
332
336
  if run_htab not in [-1, 0]:
333
- logging.error("Invalid value for `Run HTab`. Expected `0` or `-1`.")
334
337
  raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
335
338
  if use_ib_tables not in [-1, 0]:
336
- logging.error("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
337
339
  raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
338
340
  try:
339
- logging.info(f"Reading the file: {file_path}")
340
341
  with open(file_path, 'r') as file:
341
342
  lines = file.readlines()
342
- logging.info("Updating the file with new settings...")
343
343
  updated_lines = []
344
344
  for line in lines:
345
345
  if line.lstrip().startswith("Run HTab="):
346
346
  updated_line = f"Run HTab= {run_htab} \n"
347
347
  updated_lines.append(updated_line)
348
- logging.info(f"Updated 'Run HTab' to {run_htab}")
349
348
  elif line.lstrip().startswith("UNET Use Existing IB Tables="):
350
349
  updated_line = f"UNET Use Existing IB Tables= {use_ib_tables} \n"
351
350
  updated_lines.append(updated_line)
352
- logging.info(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables}")
353
351
  else:
354
352
  updated_lines.append(line)
355
- logging.info(f"Writing the updated settings back to the file: {file_path}")
356
353
  with open(file_path, 'w') as file:
357
354
  file.writelines(updated_lines)
358
- logging.info("File update completed successfully.")
359
355
  except FileNotFoundError:
360
- logging.error(f"The file '{file_path}' does not exist.")
361
356
  raise FileNotFoundError(f"The file '{file_path}' does not exist.")
362
357
  except IOError as e:
363
- logging.error(f"An error occurred while reading or writing the file: {e}")
364
358
  raise IOError(f"An error occurred while reading or writing the file: {e}")
365
359
 
366
360
  # Update the ras object's dataframes
@@ -369,9 +363,8 @@ class RasPlan:
369
363
  ras_obj.flow_df = ras_obj.get_flow_entries()
370
364
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
371
365
 
372
- # Get Functions to retrieve file paths for plan, flow, unsteady, geometry and results files
373
-
374
366
  @staticmethod
367
+ @log_call
375
368
  def get_results_path(plan_number: str, ras_object=None) -> Optional[str]:
376
369
  """
377
370
  Retrieve the results file path for a given HEC-RAS plan number.
@@ -403,24 +396,18 @@ class RasPlan:
403
396
  # Ensure plan_number is a string
404
397
  plan_number = str(plan_number).zfill(2)
405
398
 
406
- # Log the plan dataframe for debugging
407
- logging.debug("Plan DataFrame:")
408
- logging.debug(ras_obj.plan_df)
409
-
410
399
  plan_entry = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == plan_number]
411
400
  if not plan_entry.empty:
412
401
  results_path = plan_entry['HDF_Results_Path'].iloc[0]
413
402
  if results_path and Path(results_path).exists():
414
- logging.info(f"Results file for Plan number {plan_number} exists at: {results_path}")
415
403
  return results_path
416
404
  else:
417
- logging.warning(f"Results file for Plan number {plan_number} does not exist.")
418
405
  return None
419
406
  else:
420
- logging.warning(f"Plan number {plan_number} not found in the entries.")
421
407
  return None
422
408
 
423
409
  @staticmethod
410
+ @log_call
424
411
  def get_plan_path(plan_number: str, ras_object=None) -> Optional[str]:
425
412
  """
426
413
  Return the full path for a given plan number.
@@ -456,13 +443,12 @@ class RasPlan:
456
443
 
457
444
  if not plan_path.empty:
458
445
  full_path = plan_path['full_path'].iloc[0]
459
- logging.info(f"Plan file for Plan number {plan_number} found at: {full_path}")
460
446
  return full_path
461
447
  else:
462
- logging.warning(f"Plan number {plan_number} not found in the updated plan entries.")
463
448
  return None
464
449
 
465
450
  @staticmethod
451
+ @log_call
466
452
  def get_flow_path(flow_number: str, ras_object=None) -> Optional[str]:
467
453
  """
468
454
  Return the full path for a given flow number.
@@ -494,13 +480,12 @@ class RasPlan:
494
480
  flow_path = ras_obj.flow_df[ras_obj.flow_df['flow_number'] == flow_number]
495
481
  if not flow_path.empty:
496
482
  full_path = flow_path['full_path'].iloc[0]
497
- logging.info(f"Flow file for Flow number {flow_number} found at: {full_path}")
498
483
  return full_path
499
484
  else:
500
- logging.warning(f"Flow number {flow_number} not found in the updated flow entries.")
501
485
  return None
502
486
 
503
487
  @staticmethod
488
+ @log_call
504
489
  def get_unsteady_path(unsteady_number: str, ras_object=None) -> Optional[str]:
505
490
  """
506
491
  Return the full path for a given unsteady number.
@@ -532,13 +517,12 @@ class RasPlan:
532
517
  unsteady_path = ras_obj.unsteady_df[ras_obj.unsteady_df['unsteady_number'] == unsteady_number]
533
518
  if not unsteady_path.empty:
534
519
  full_path = unsteady_path['full_path'].iloc[0]
535
- logging.info(f"Unsteady file for Unsteady number {unsteady_number} found at: {full_path}")
536
520
  return full_path
537
521
  else:
538
- logging.warning(f"Unsteady number {unsteady_number} not found in the updated unsteady entries.")
539
522
  return None
540
523
 
541
524
  @staticmethod
525
+ @log_call
542
526
  def get_geom_path(geom_number: str, ras_object=None) -> Optional[str]:
543
527
  """
544
528
  Return the full path for a given geometry number.
@@ -570,15 +554,14 @@ class RasPlan:
570
554
  geom_path = ras_obj.geom_df[ras_obj.geom_df['geom_number'] == geom_number]
571
555
  if not geom_path.empty:
572
556
  full_path = geom_path['full_path'].iloc[0]
573
- logging.info(f"Geometry file for Geom number {geom_number} found at: {full_path}")
574
557
  return full_path
575
558
  else:
576
- logging.warning(f"Geometry number {geom_number} not found in the updated geometry entries.")
577
559
  return None
578
560
 
579
561
  # Clone Functions to copy unsteady, flow, and geometry files from templates
580
562
 
581
563
  @staticmethod
564
+ @log_call
582
565
  def clone_plan(template_plan, new_plan_shortid=None, ras_object=None):
583
566
  """
584
567
  Create a new plan file based on a template and update the project file.
@@ -610,18 +593,15 @@ class RasPlan:
610
593
  new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
611
594
 
612
595
  if not template_plan_path.exists():
613
- logging.error(f"Template plan file '{template_plan_path}' does not exist.")
614
596
  raise FileNotFoundError(f"Template plan file '{template_plan_path}' does not exist.")
615
597
 
616
598
  shutil.copy(template_plan_path, new_plan_path)
617
- logging.info(f"Copied {template_plan_path} to {new_plan_path}")
618
599
 
619
600
  try:
620
601
  with open(new_plan_path, 'r') as f:
621
602
  plan_lines = f.readlines()
622
603
  except FileNotFoundError:
623
- logging.error(f"New plan file not found after copying: {new_plan_path}")
624
- raise
604
+ raise FileNotFoundError(f"New plan file not found after copying: {new_plan_path}")
625
605
 
626
606
  shortid_pattern = re.compile(r'^Short Identifier=(.*)$', re.IGNORECASE)
627
607
  for i, line in enumerate(plan_lines):
@@ -633,23 +613,19 @@ class RasPlan:
633
613
  else:
634
614
  new_shortid = new_plan_shortid[:24]
635
615
  plan_lines[i] = f"Short Identifier={new_shortid}\n"
636
- logging.info(f"Updated 'Short Identifier' to '{new_shortid}' in {new_plan_path}")
637
616
  break
638
617
 
639
618
  try:
640
619
  with open(new_plan_path, 'w') as f:
641
620
  f.writelines(plan_lines)
642
- logging.info(f"Updated short identifier in {new_plan_path}")
643
621
  except IOError as e:
644
- logging.error(f"Failed to write updated short identifier to {new_plan_path}: {e}")
645
- raise
622
+ raise IOError(f"Failed to write updated short identifier to {new_plan_path}: {e}")
646
623
 
647
624
  try:
648
625
  with open(ras_obj.prj_file, 'r') as f:
649
626
  lines = f.readlines()
650
627
  except FileNotFoundError:
651
- logging.error(f"Project file not found: {ras_obj.prj_file}")
652
- raise
628
+ raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
653
629
 
654
630
  # Prepare the new Plan File entry line
655
631
  new_plan_line = f"Plan File=p{new_plan_num}\n"
@@ -669,27 +645,22 @@ class RasPlan:
669
645
 
670
646
  if insertion_index is not None:
671
647
  lines.insert(insertion_index, new_plan_line)
672
- logging.info(f"Inserted new plan line at index {insertion_index}")
673
648
  else:
674
649
  # Try to insert after the last Plan File entry
675
650
  plan_indices = [i for i, line in enumerate(lines) if plan_file_pattern.match(line.strip())]
676
651
  if plan_indices:
677
652
  last_plan_index = plan_indices[-1]
678
653
  lines.insert(last_plan_index + 1, new_plan_line)
679
- logging.info(f"Inserted new plan line after index {last_plan_index}")
680
654
  else:
681
655
  # Append at the end if no Plan File entries exist
682
656
  lines.append(new_plan_line)
683
- logging.info(f"Appended new plan line at the end of the project file")
684
657
 
685
658
  try:
686
659
  # Write the updated lines back to the project file
687
660
  with open(ras_obj.prj_file, 'w') as f:
688
661
  f.writelines(lines)
689
- logging.info(f"Updated {ras_obj.prj_file} with new plan p{new_plan_num}")
690
662
  except IOError as e:
691
- logging.error(f"Failed to write updated project file: {e}")
692
- raise
663
+ raise IOError(f"Failed to write updated project file: {e}")
693
664
 
694
665
  new_plan = new_plan_num
695
666
 
@@ -704,6 +675,7 @@ class RasPlan:
704
675
  return new_plan
705
676
 
706
677
  @staticmethod
678
+ @log_call
707
679
  def clone_unsteady(template_unsteady, ras_object=None):
708
680
  """
709
681
  Copy unsteady flow files from a template, find the next unsteady number,
@@ -735,27 +707,21 @@ class RasPlan:
735
707
  new_unsteady_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}"
736
708
 
737
709
  if not template_unsteady_path.exists():
738
- logging.error(f"Template unsteady file '{template_unsteady_path}' does not exist.")
739
710
  raise FileNotFoundError(f"Template unsteady file '{template_unsteady_path}' does not exist.")
740
711
 
741
712
  shutil.copy(template_unsteady_path, new_unsteady_path)
742
- logging.info(f"Copied {template_unsteady_path} to {new_unsteady_path}")
743
713
 
744
714
  # Copy the corresponding .hdf file if it exists
745
715
  template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{template_unsteady}.hdf"
746
716
  new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_num}.hdf"
747
717
  if template_hdf_path.exists():
748
718
  shutil.copy(template_hdf_path, new_hdf_path)
749
- logging.info(f"Copied {template_hdf_path} to {new_hdf_path}")
750
- else:
751
- logging.warning(f"No corresponding .hdf file found for '{template_unsteady_path}'. Skipping '.hdf' copy.")
752
719
 
753
720
  try:
754
721
  with open(ras_obj.prj_file, 'r') as f:
755
722
  lines = f.readlines()
756
723
  except FileNotFoundError:
757
- logging.error(f"Project file not found: {ras_obj.prj_file}")
758
- raise
724
+ raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
759
725
 
760
726
  # Prepare the new Unsteady Flow File entry line
761
727
  new_unsteady_line = f"Unsteady File=u{new_unsteady_num}\n"
@@ -775,27 +741,22 @@ class RasPlan:
775
741
 
776
742
  if insertion_index is not None:
777
743
  lines.insert(insertion_index, new_unsteady_line)
778
- logging.info(f"Inserted new unsteady flow line at index {insertion_index}")
779
744
  else:
780
745
  # Try to insert after the last Unsteady Flow File entry
781
746
  unsteady_indices = [i for i, line in enumerate(lines) if unsteady_file_pattern.match(line.strip())]
782
747
  if unsteady_indices:
783
748
  last_unsteady_index = unsteady_indices[-1]
784
749
  lines.insert(last_unsteady_index + 1, new_unsteady_line)
785
- logging.info(f"Inserted new unsteady flow line after index {last_unsteady_index}")
786
750
  else:
787
751
  # Append at the end if no Unsteady Flow File entries exist
788
752
  lines.append(new_unsteady_line)
789
- logging.info(f"Appended new unsteady flow line at the end of the project file")
790
753
 
791
754
  try:
792
755
  # Write the updated lines back to the project file
793
756
  with open(ras_obj.prj_file, 'w') as f:
794
757
  f.writelines(lines)
795
- logging.info(f"Updated {ras_obj.prj_file} with new unsteady flow file u{new_unsteady_num}")
796
758
  except IOError as e:
797
- logging.error(f"Failed to write updated project file: {e}")
798
- raise
759
+ raise IOError(f"Failed to write updated project file: {e}")
799
760
 
800
761
  new_unsteady = new_unsteady_num
801
762
 
@@ -810,6 +771,7 @@ class RasPlan:
810
771
  return new_unsteady
811
772
 
812
773
  @staticmethod
774
+ @log_call
813
775
  def clone_steady(template_flow, ras_object=None):
814
776
  """
815
777
  Copy steady flow files from a template, find the next flow number,
@@ -841,19 +803,16 @@ class RasPlan:
841
803
  new_flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_flow_num}"
842
804
 
843
805
  if not template_flow_path.exists():
844
- logging.error(f"Template steady flow file '{template_flow_path}' does not exist.")
845
806
  raise FileNotFoundError(f"Template steady flow file '{template_flow_path}' does not exist.")
846
807
 
847
808
  shutil.copy(template_flow_path, new_flow_path)
848
- logging.info(f"Copied {template_flow_path} to {new_flow_path}")
849
809
 
850
810
  # Read the contents of the project file
851
811
  try:
852
812
  with open(ras_obj.prj_file, 'r') as f:
853
813
  lines = f.readlines()
854
814
  except FileNotFoundError:
855
- logging.error(f"Project file not found: {ras_obj.prj_file}")
856
- raise
815
+ raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
857
816
 
858
817
  # Prepare the new Steady Flow File entry line
859
818
  new_flow_line = f"Flow File=f{new_flow_num}\n"
@@ -873,27 +832,22 @@ class RasPlan:
873
832
 
874
833
  if insertion_index is not None:
875
834
  lines.insert(insertion_index, new_flow_line)
876
- logging.info(f"Inserted new steady flow line at index {insertion_index}")
877
835
  else:
878
836
  # Try to insert after the last Steady Flow File entry
879
837
  flow_indices = [i for i, line in enumerate(lines) if flow_file_pattern.match(line.strip())]
880
838
  if flow_indices:
881
839
  last_flow_index = flow_indices[-1]
882
840
  lines.insert(last_flow_index + 1, new_flow_line)
883
- logging.info(f"Inserted new steady flow line after index {last_flow_index}")
884
841
  else:
885
842
  # Append at the end if no Steady Flow File entries exist
886
843
  lines.append(new_flow_line)
887
- logging.info(f"Appended new steady flow line at the end of the project file")
888
844
 
889
845
  try:
890
846
  # Write the updated lines back to the project file
891
847
  with open(ras_obj.prj_file, 'w') as f:
892
848
  f.writelines(lines)
893
- logging.info(f"Updated {ras_obj.prj_file} with new steady flow file f{new_flow_num}")
894
849
  except IOError as e:
895
- logging.error(f"Failed to write updated project file: {e}")
896
- raise
850
+ raise IOError(f"Failed to write updated project file: {e}")
897
851
 
898
852
  new_steady = new_flow_num
899
853
 
@@ -908,6 +862,7 @@ class RasPlan:
908
862
  return new_steady
909
863
 
910
864
  @staticmethod
865
+ @log_call
911
866
  def clone_geom(template_geom, ras_object=None):
912
867
  """
913
868
  Copy geometry files from a template, find the next geometry number,
@@ -928,13 +883,11 @@ class RasPlan:
928
883
 
929
884
  # Update geometry entries without reinitializing the entire project
930
885
  ras_obj.geom_df = ras_obj.get_prj_entries('Geom')
931
- logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
932
886
 
933
887
  template_geom_filename = f"{ras_obj.project_name}.g{template_geom}"
934
888
  template_geom_path = ras_obj.project_folder / template_geom_filename
935
889
 
936
890
  if not template_geom_path.is_file():
937
- logging.error(f"Template geometry file '{template_geom_path}' does not exist.")
938
891
  raise FileNotFoundError(f"Template geometry file '{template_geom_path}' does not exist.")
939
892
 
940
893
  next_geom_number = RasPlan.get_next_number(ras_obj.geom_df['geom_number'])
@@ -943,23 +896,18 @@ class RasPlan:
943
896
  new_geom_path = ras_obj.project_folder / new_geom_filename
944
897
 
945
898
  shutil.copyfile(template_geom_path, new_geom_path)
946
- logging.info(f"Copied '{template_geom_path}' to '{new_geom_path}'.")
947
899
 
948
900
  # Handle HDF file copy
949
901
  template_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{template_geom}.hdf"
950
902
  new_hdf_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{next_geom_number}.hdf"
951
903
  if template_hdf_path.is_file():
952
904
  shutil.copyfile(template_hdf_path, new_hdf_path)
953
- logging.info(f"Copied '{template_hdf_path}' to '{new_hdf_path}'.")
954
- else:
955
- logging.warning(f"Template geometry HDF file '{template_hdf_path}' does not exist. Skipping '.hdf' copy.")
956
905
 
957
906
  try:
958
907
  with open(ras_obj.prj_file, 'r') as file:
959
908
  lines = file.readlines()
960
909
  except FileNotFoundError:
961
- logging.error(f"Project file not found: {ras_obj.prj_file}")
962
- raise
910
+ raise FileNotFoundError(f"Project file not found: {ras_obj.prj_file}")
963
911
 
964
912
  # Prepare the new Geometry File entry line
965
913
  new_geom_line = f"Geom File=g{next_geom_number}\n"
@@ -979,27 +927,22 @@ class RasPlan:
979
927
 
980
928
  if insertion_index is not None:
981
929
  lines.insert(insertion_index, new_geom_line)
982
- logging.info(f"Inserted new geometry line at index {insertion_index}")
983
930
  else:
984
931
  # Try to insert after the last Geometry File entry
985
932
  geom_indices = [i for i, line in enumerate(lines) if geom_file_pattern.match(line.strip())]
986
933
  if geom_indices:
987
934
  last_geom_index = geom_indices[-1]
988
935
  lines.insert(last_geom_index + 1, new_geom_line)
989
- logging.info(f"Inserted new geometry line after index {last_geom_index}")
990
936
  else:
991
937
  # Append at the end if no Geometry File entries exist
992
938
  lines.append(new_geom_line)
993
- logging.info(f"Appended new geometry line at the end of the project file")
994
939
 
995
940
  try:
996
941
  # Write the updated lines back to the project file
997
942
  with open(ras_obj.prj_file, 'w') as file:
998
943
  file.writelines(lines)
999
- logging.info(f"Updated {ras_obj.prj_file} with new geometry file g{next_geom_number}")
1000
944
  except IOError as e:
1001
- logging.error(f"Failed to write updated project file: {e}")
1002
- raise
945
+ raise IOError(f"Failed to write updated project file: {e}")
1003
946
 
1004
947
  new_geom = next_geom_number
1005
948
 
@@ -1009,11 +952,10 @@ class RasPlan:
1009
952
  ras_obj.flow_df = ras_obj.get_flow_entries()
1010
953
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
1011
954
 
1012
- logging.debug(f"Updated geometry entries:\n{ras_obj.geom_df}")
1013
-
1014
955
  return new_geom
1015
956
 
1016
957
  @staticmethod
958
+ @log_call
1017
959
  def get_next_number(existing_numbers):
1018
960
  """
1019
961
  Determine the next available number from a list of existing numbers.
@@ -1041,8 +983,8 @@ class RasPlan:
1041
983
  break
1042
984
  return f"{next_number:02d}"
1043
985
 
1044
-
1045
986
  @staticmethod
987
+ @log_call
1046
988
  def get_plan_value(
1047
989
  plan_number_or_path: Union[str, Path],
1048
990
  key: str,
@@ -1064,47 +1006,51 @@ class RasPlan:
1064
1006
  IOError: If there's an error reading the plan file
1065
1007
 
1066
1008
  Available keys and their expected types:
1067
- - 'description' (str): Plan description
1068
- - 'computation_interval' (str): Time value for computational time step (e.g., '5SEC', '2MIN')
1069
- - 'dss_file' (str): Name of the DSS file used
1070
- - 'flow_file' (str): Name of the flow input file
1071
- - 'friction_slope_method' (int): Method selection for friction slope (e.g., 1, 2)
1072
- - 'geom_file' (str): Name of the geometry input file
1073
- - 'mapping_interval' (str): Time interval for mapping output
1074
- - 'plan_file' (str): Name of the plan file
1075
- - 'plan_title' (str): Title of the simulation plan
1076
- - 'program_version' (str): Version number of HEC-RAS
1077
- - 'run_htab' (int): Flag to run HTab module (-1 or 1)
1078
- - 'run_post_process' (int): Flag to run post-processing (-1 or 1)
1079
- - 'run_sediment' (int): Flag to run sediment transport module (0 or 1)
1080
- - 'run_unet' (int): Flag to run unsteady network module (-1 or 1)
1081
- - 'run_wqnet' (int): Flag to run water quality module (0 or 1)
1082
- - 'short_identifier' (str): Short name or ID for the plan
1083
- - 'simulation_date' (str): Start and end dates/times for simulation
1084
- - 'unet_d1_cores' (int): Number of cores used in 1D calculations
1085
- - 'unet_use_existing_ib_tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
1086
- - 'unet_1d_methodology' (str): 1D calculation methodology
1087
- - 'unet_d2_solver_type' (str): 2D solver type
1088
- - 'unet_d2_name' (str): Name of the 2D area
1089
- - 'run_rasmapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
1009
+ - 'Computation Interval' (str): Time value for computational time step (e.g., '5SEC', '2MIN')
1010
+ - 'DSS File' (str): Name of the DSS file used
1011
+ - 'Flow File' (str): Name of the flow input file
1012
+ - 'Friction Slope Method' (int): Method selection for friction slope (e.g., 1, 2)
1013
+ - 'Geom File' (str): Name of the geometry input file
1014
+ - 'Mapping Interval' (str): Time interval for mapping output
1015
+ - 'Plan File' (str): Name of the plan file
1016
+ - 'Plan Title' (str): Title of the simulation plan
1017
+ - 'Program Version' (str): Version number of HEC-RAS
1018
+ - 'Run HTAB' (int): Flag to run HTab module (-1 or 1)
1019
+ - 'Run Post Process' (int): Flag to run post-processing (-1 or 1)
1020
+ - 'Run Sediment' (int): Flag to run sediment transport module (0 or 1)
1021
+ - 'Run UNET' (int): Flag to run unsteady network module (-1 or 1)
1022
+ - 'Run WQNET' (int): Flag to run water quality module (0 or 1)
1023
+ - 'Short Identifier' (str): Short name or ID for the plan
1024
+ - 'Simulation Date' (str): Start and end dates/times for simulation
1025
+ - 'UNET D1 Cores' (int): Number of cores used in 1D calculations
1026
+ - 'UNET Use Existing IB Tables' (int): Flag for using existing internal boundary tables (-1, 0, or 1)
1027
+ - 'UNET 1D Methodology' (str): 1D calculation methodology
1028
+ - 'UNET D2 Solver Type' (str): 2D solver type
1029
+ - 'UNET D2 Name' (str): Name of the 2D area
1030
+ - 'Run RASMapper' (int): Flag to run RASMapper for floodplain mapping (-1 for off, 0 for on)
1031
+
1032
+
1033
+ Note:
1034
+ Writing Multi line keys like 'Description' are not supported by this function.
1090
1035
 
1091
1036
  Example:
1092
- >>> computation_interval = RasPlan.get_plan_value("01", "computation_interval")
1037
+ >>> computation_interval = RasPlan.get_plan_value("01", "Computation Interval")
1093
1038
  >>> print(f"Computation interval: {computation_interval}")
1094
1039
  """
1095
1040
  ras_obj = ras_object or ras
1096
1041
  ras_obj.check_initialized()
1097
1042
 
1098
- valid_keys = {
1099
- 'description', 'computation_interval', 'dss_file', 'flow_file', 'friction_slope_method',
1100
- 'geom_file', 'mapping_interval', 'plan_file', 'plan_title', 'program_version',
1101
- 'run_htab', 'run_post_process', 'run_sediment', 'run_unet', 'run_wqnet',
1102
- 'short_identifier', 'simulation_date', 'unet_d1_cores', 'unet_use_existing_ib_tables',
1103
- 'unet_1d_methodology', 'unet_d2_solver_type', 'unet_d2_name', 'run_rasmapper'
1043
+ supported_plan_keys = {
1044
+ 'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
1045
+ 'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
1046
+ 'Run HTAB', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
1047
+ 'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET Use Existing IB Tables',
1048
+ 'UNET 1D Methodology', 'UNET D2 Solver Type', 'UNET D2 Name', 'Run RASMapper'
1104
1049
  }
1105
1050
 
1106
- if key not in valid_keys:
1107
- logging.warning(f"Unknown key: {key}. Valid keys are: {', '.join(valid_keys)}\n Add more keys and explanations in get_plan_value() as needed.")
1051
+ if key not in supported_plan_keys:
1052
+ logger = logging.getLogger(__name__)
1053
+ logger.warning(f"Unknown key: {key}. Valid keys are: {', '.join(supported_plan_keys)}\n Add more keys and explanations in get_plan_value() as needed.")
1108
1054
 
1109
1055
  plan_file_path = Path(plan_number_or_path)
1110
1056
  if not plan_file_path.is_file():
@@ -1116,22 +1062,25 @@ class RasPlan:
1116
1062
  with open(plan_file_path, 'r') as file:
1117
1063
  content = file.read()
1118
1064
  except IOError as e:
1119
- logging.error(f"Error reading plan file {plan_file_path}: {e}")
1065
+ logger = logging.getLogger(__name__)
1066
+ logger.error(f"Error reading plan file {plan_file_path}: {e}")
1120
1067
  raise
1121
1068
 
1122
- if key == 'description':
1069
+ if key == 'Description':
1123
1070
  match = re.search(r'Begin DESCRIPTION(.*?)END DESCRIPTION', content, re.DOTALL)
1124
1071
  return match.group(1).strip() if match else None
1125
1072
  else:
1126
- pattern = f"{key.replace('_', ' ').title()}=(.*)"
1073
+ pattern = f"{key}=(.*)"
1127
1074
  match = re.search(pattern, content)
1128
1075
  if match:
1129
1076
  return match.group(1).strip()
1130
1077
  else:
1131
- logging.error(f"Key '{key}' not found in the plan file.")
1078
+ logger = logging.getLogger(__name__)
1079
+ logger.error(f"Key '{key}' not found in the plan file.")
1132
1080
  return None
1133
1081
 
1134
1082
  @staticmethod
1083
+ @log_call
1135
1084
  def update_plan_value(
1136
1085
  plan_number_or_path: Union[str, Path],
1137
1086
  key: str,
@@ -1161,16 +1110,16 @@ class RasPlan:
1161
1110
  ras_obj = ras_object or ras
1162
1111
  ras_obj.check_initialized()
1163
1112
 
1164
- valid_keys = {
1165
- 'description', 'computation_interval', 'dss_file', 'flow_file', 'friction_slope_method',
1166
- 'geom_file', 'mapping_interval', 'plan_file', 'plan_title', 'program_version',
1167
- 'run_htab', 'run_post_process', 'run_sediment', 'run_unet', 'run_wqnet',
1168
- 'short_identifier', 'simulation_date', 'unet_d1_cores', 'unet_use_existing_ib_tables',
1169
- 'unet_1d_methodology', 'unet_d2_solver_type', 'unet_d2_name', 'run_rasmapper'
1113
+ supported_plan_keys = {
1114
+ 'Description', 'Computation Interval', 'DSS File', 'Flow File', 'Friction Slope Method',
1115
+ 'Geom File', 'Mapping Interval', 'Plan File', 'Plan Title', 'Program Version',
1116
+ 'Run HTAB', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
1117
+ 'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET Use Existing IB Tables',
1118
+ 'UNET 1D Methodology', 'UNET D2 Solver Type', 'UNET D2 Name', 'Run RASMapper'
1170
1119
  }
1171
-
1172
- if key not in valid_keys:
1173
- logging.warning(f"Unknown key: {key}. Valid keys are: {', '.join(valid_keys)}")
1120
+ logger = logging.getLogger(__name__)
1121
+ if key not in supported_plan_keys:
1122
+ logger.warning(f"Unknown key: {key}. Valid keys are: {', '.join(supported_plan_keys)}")
1174
1123
 
1175
1124
  plan_file_path = Path(plan_number_or_path)
1176
1125
  if not plan_file_path.is_file():
@@ -1182,7 +1131,7 @@ class RasPlan:
1182
1131
  with open(plan_file_path, 'r') as file:
1183
1132
  lines = file.readlines()
1184
1133
  except IOError as e:
1185
- logging.error(f"Error reading plan file {plan_file_path}: {e}")
1134
+ logger.error(f"Error reading plan file {plan_file_path}: {e}")
1186
1135
  raise
1187
1136
 
1188
1137
  # Special handling for description
@@ -1209,15 +1158,15 @@ class RasPlan:
1209
1158
  updated = True
1210
1159
  break
1211
1160
  if not updated:
1212
- logging.error(f"Key '{key}' not found in the plan file.")
1161
+ logger.error(f"Key '{key}' not found in the plan file.")
1213
1162
  return
1214
1163
 
1215
1164
  try:
1216
1165
  with open(plan_file_path, 'w') as file:
1217
1166
  file.writelines(lines)
1218
- logging.info(f"Updated {key} in plan file: {plan_file_path}")
1167
+ logger.info(f"Updated {key} in plan file: {plan_file_path}")
1219
1168
  except IOError as e:
1220
- logging.error(f"Error writing to plan file {plan_file_path}: {e}")
1169
+ logger.error(f"Error writing to plan file {plan_file_path}: {e}")
1221
1170
  raise
1222
1171
 
1223
1172
  # Refresh RasPrj dataframes