ras-commander 0.35.0__py3-none-any.whl → 0.36.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.
@@ -1,21 +1,35 @@
1
1
  """
2
- Operations for handling unsteady flow files in HEC-RAS projects.
2
+ RasUnsteady - Operations for handling unsteady flow 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
+
18
+
19
+ Example:
20
+ @log_call
21
+ def my_function():
22
+ logger.debug("Additional debug information")
23
+ # Function logic here
3
24
  """
25
+ import os
4
26
  from pathlib import Path
5
27
  from .RasPrj import ras
6
- import logging
7
- import re
28
+ from ras_commander.logging_config import get_logger, log_call
29
+
30
+ logger = get_logger(__name__)
8
31
 
9
- # Configure logging at the module level
10
- logging.basicConfig(
11
- level=logging.INFO, # Set to DEBUG for more detailed output
12
- format='%(asctime)s - %(levelname)s - %(message)s',
13
- handlers=[
14
- logging.StreamHandler(), # Logs to console
15
- # Uncomment the next line to enable logging to a file
16
- # logging.FileHandler('ras_unsteady.log')
17
- ]
18
- )
32
+ # Module code starts here
19
33
 
20
34
  class RasUnsteady:
21
35
  """
@@ -23,6 +37,7 @@ class RasUnsteady:
23
37
  """
24
38
 
25
39
  @staticmethod
40
+ @log_call
26
41
  def update_unsteady_parameters(unsteady_file, modifications, ras_object=None):
27
42
  """
28
43
  Modify parameters in an unsteady flow file.
@@ -58,12 +73,12 @@ class RasUnsteady:
58
73
  try:
59
74
  with open(unsteady_path, 'r') as f:
60
75
  lines = f.readlines()
61
- logging.debug(f"Successfully read unsteady flow file: {unsteady_path}")
76
+ logger.debug(f"Successfully read unsteady flow file: {unsteady_path}")
62
77
  except FileNotFoundError:
63
- logging.error(f"Unsteady flow file not found: {unsteady_path}")
78
+ logger.error(f"Unsteady flow file not found: {unsteady_path}")
64
79
  raise FileNotFoundError(f"Unsteady flow file not found: {unsteady_path}")
65
80
  except PermissionError:
66
- logging.error(f"Permission denied when reading unsteady flow file: {unsteady_path}")
81
+ logger.error(f"Permission denied when reading unsteady flow file: {unsteady_path}")
67
82
  raise PermissionError(f"Permission denied when reading unsteady flow file: {unsteady_path}")
68
83
 
69
84
  updated = False
@@ -73,21 +88,21 @@ class RasUnsteady:
73
88
  old_value = line.strip().split('=')[1]
74
89
  lines[i] = f"{param}={new_value}\n"
75
90
  updated = True
76
- logging.info(f"Updated {param} from {old_value} to {new_value}")
91
+ logger.info(f"Updated {param} from {old_value} to {new_value}")
77
92
 
78
93
  if updated:
79
94
  try:
80
95
  with open(unsteady_path, 'w') as f:
81
96
  f.writelines(lines)
82
- logging.debug(f"Successfully wrote modifications to unsteady flow file: {unsteady_path}")
97
+ logger.debug(f"Successfully wrote modifications to unsteady flow file: {unsteady_path}")
83
98
  except PermissionError:
84
- logging.error(f"Permission denied when writing to unsteady flow file: {unsteady_path}")
99
+ logger.error(f"Permission denied when writing to unsteady flow file: {unsteady_path}")
85
100
  raise PermissionError(f"Permission denied when writing to unsteady flow file: {unsteady_path}")
86
101
  except IOError as e:
87
- logging.error(f"Error writing to unsteady flow file: {unsteady_path}. {str(e)}")
102
+ logger.error(f"Error writing to unsteady flow file: {unsteady_path}. {str(e)}")
88
103
  raise IOError(f"Error writing to unsteady flow file: {unsteady_path}. {str(e)}")
89
- logging.info(f"Applied modifications to {unsteady_file}")
104
+ logger.info(f"Applied modifications to {unsteady_file}")
90
105
  else:
91
- logging.warning(f"No matching parameters found in {unsteady_file}")
106
+ logger.warning(f"No matching parameters found in {unsteady_file}")
92
107
 
93
108
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
ras_commander/RasUtils.py CHANGED
@@ -1,21 +1,38 @@
1
1
  """
2
- Utility functions for the ras-commander library.
2
+ RasUtils - Utility functions for the ras-commander library
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
+
18
+ Example:
19
+ @log_call
20
+ def my_function():
21
+ logger.debug("Additional debug information")
22
+ # Function logic here
3
23
  """
4
24
  import os
5
- import shutil
6
- import logging
7
- import time
8
25
  from pathlib import Path
9
26
  from .RasPrj import ras
10
27
  from typing import Union, Optional, Dict
11
28
  import pandas as pd
12
29
  import numpy as np
30
+ import shutil
31
+ from ras_commander import get_logger
32
+ from ras_commander.logging_config import get_logger, log_call
13
33
 
14
- # Configure logging
15
- logging.basicConfig(
16
- level=logging.INFO,
17
- format='%(asctime)s - %(levelname)s - %(message)s'
18
- )
34
+ logger = get_logger(__name__)
35
+ # Module code starts here
19
36
 
20
37
  class RasUtils:
21
38
  """
@@ -24,6 +41,7 @@ class RasUtils:
24
41
  """
25
42
 
26
43
  @staticmethod
44
+ @log_call
27
45
  def create_backup(file_path: Path, backup_suffix: str = "_backup", ras_object=None) -> Path:
28
46
  """
29
47
  Create a backup of the specified file.
@@ -47,13 +65,14 @@ class RasUtils:
47
65
  backup_path = original_path.with_name(f"{original_path.stem}{backup_suffix}{original_path.suffix}")
48
66
  try:
49
67
  shutil.copy2(original_path, backup_path)
50
- logging.info(f"Backup created: {backup_path}")
68
+ logger.info(f"Backup created: {backup_path}")
51
69
  except Exception as e:
52
- logging.error(f"Failed to create backup for {original_path}: {e}")
70
+ logger.error(f"Failed to create backup for {original_path}: {e}")
53
71
  raise
54
72
  return backup_path
55
73
 
56
74
  @staticmethod
75
+ @log_call
57
76
  def restore_from_backup(backup_path: Path, remove_backup: bool = True, ras_object=None) -> Path:
58
77
  """
59
78
  Restore a file from its backup.
@@ -75,23 +94,24 @@ class RasUtils:
75
94
 
76
95
  backup_path = Path(backup_path)
77
96
  if '_backup' not in backup_path.stem:
78
- logging.error(f"Backup suffix '_backup' not found in {backup_path.name}")
97
+ logger.error(f"Backup suffix '_backup' not found in {backup_path.name}")
79
98
  raise ValueError(f"Backup suffix '_backup' not found in {backup_path.name}")
80
99
 
81
100
  original_stem = backup_path.stem.rsplit('_backup', 1)[0]
82
101
  original_path = backup_path.with_name(f"{original_stem}{backup_path.suffix}")
83
102
  try:
84
103
  shutil.copy2(backup_path, original_path)
85
- logging.info(f"File restored: {original_path}")
104
+ logger.info(f"File restored: {original_path}")
86
105
  if remove_backup:
87
106
  backup_path.unlink()
88
- logging.info(f"Backup removed: {backup_path}")
107
+ logger.info(f"Backup removed: {backup_path}")
89
108
  except Exception as e:
90
- logging.error(f"Failed to restore from backup {backup_path}: {e}")
109
+ logger.error(f"Failed to restore from backup {backup_path}: {e}")
91
110
  raise
92
111
  return original_path
93
112
 
94
113
  @staticmethod
114
+ @log_call
95
115
  def create_directory(directory_path: Path, ras_object=None) -> Path:
96
116
  """
97
117
  Ensure that a directory exists, creating it if necessary.
@@ -113,13 +133,14 @@ class RasUtils:
113
133
  path = Path(directory_path)
114
134
  try:
115
135
  path.mkdir(parents=True, exist_ok=True)
116
- logging.info(f"Directory ensured: {path}")
136
+ logger.info(f"Directory ensured: {path}")
117
137
  except Exception as e:
118
- logging.error(f"Failed to create directory {path}: {e}")
138
+ logger.error(f"Failed to create directory {path}: {e}")
119
139
  raise
120
140
  return path
121
141
 
122
142
  @staticmethod
143
+ @log_call
123
144
  def find_files_by_extension(extension: str, ras_object=None) -> list:
124
145
  """
125
146
  List all files in the project directory with a specific extension.
@@ -141,13 +162,14 @@ class RasUtils:
141
162
  try:
142
163
  files = list(ras_obj.project_folder.glob(f"*{extension}"))
143
164
  file_list = [str(file) for file in files]
144
- logging.info(f"Found {len(file_list)} files with extension '{extension}' in {ras_obj.project_folder}")
165
+ logger.info(f"Found {len(file_list)} files with extension '{extension}' in {ras_obj.project_folder}")
145
166
  return file_list
146
167
  except Exception as e:
147
- logging.error(f"Failed to find files with extension '{extension}': {e}")
168
+ logger.error(f"Failed to find files with extension '{extension}': {e}")
148
169
  raise
149
170
 
150
171
  @staticmethod
172
+ @log_call
151
173
  def get_file_size(file_path: Path, ras_object=None) -> Optional[int]:
152
174
  """
153
175
  Get the size of a file in bytes.
@@ -170,16 +192,17 @@ class RasUtils:
170
192
  if path.exists():
171
193
  try:
172
194
  size = path.stat().st_size
173
- logging.info(f"Size of {path}: {size} bytes")
195
+ logger.info(f"Size of {path}: {size} bytes")
174
196
  return size
175
197
  except Exception as e:
176
- logging.error(f"Failed to get size for {path}: {e}")
198
+ logger.error(f"Failed to get size for {path}: {e}")
177
199
  raise
178
200
  else:
179
- logging.warning(f"File not found: {path}")
201
+ logger.warning(f"File not found: {path}")
180
202
  return None
181
203
 
182
204
  @staticmethod
205
+ @log_call
183
206
  def get_file_modification_time(file_path: Path, ras_object=None) -> Optional[float]:
184
207
  """
185
208
  Get the last modification time of a file.
@@ -195,6 +218,7 @@ class RasUtils:
195
218
  >>> mtime = RasUtils.get_file_modification_time(Path("project.prj"))
196
219
  >>> print(f"Last modified: {mtime}")
197
220
  """
221
+
198
222
  ras_obj = ras_object or ras
199
223
  ras_obj.check_initialized()
200
224
 
@@ -202,16 +226,17 @@ class RasUtils:
202
226
  if path.exists():
203
227
  try:
204
228
  mtime = path.stat().st_mtime
205
- logging.info(f"Last modification time of {path}: {mtime}")
229
+ logger.info(f"Last modification time of {path}: {mtime}")
206
230
  return mtime
207
231
  except Exception as e:
208
- logging.error(f"Failed to get modification time for {path}: {e}")
232
+ logger.exception(f"Failed to get modification time for {path}")
209
233
  raise
210
234
  else:
211
- logging.warning(f"File not found: {path}")
235
+ logger.warning(f"File not found: {path}")
212
236
  return None
213
237
 
214
238
  @staticmethod
239
+ @log_call
215
240
  def get_plan_path(current_plan_number_or_path: Union[str, Path], ras_object=None) -> Path:
216
241
  """
217
242
  Get the path for a plan file with a given plan number or path.
@@ -229,27 +254,29 @@ class RasUtils:
229
254
  >>> plan_path = RasUtils.get_plan_path("path/to/plan.p01")
230
255
  >>> print(f"Plan file path: {plan_path}")
231
256
  """
257
+
232
258
  ras_obj = ras_object or ras
233
259
  ras_obj.check_initialized()
234
260
 
235
261
  plan_path = Path(current_plan_number_or_path)
236
262
  if plan_path.is_file():
237
- logging.info(f"Using provided plan file path: {plan_path}")
263
+ logger.info(f"Using provided plan file path: {plan_path}")
238
264
  return plan_path
239
265
 
240
266
  try:
241
267
  current_plan_number = f"{int(current_plan_number_or_path):02d}" # Ensure two-digit format
242
- logging.info(f"Converted plan number to two-digit format: {current_plan_number}")
268
+ logger.debug(f"Converted plan number to two-digit format: {current_plan_number}")
243
269
  except ValueError:
244
- logging.error(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
270
+ logger.error(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
245
271
  raise ValueError(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
246
272
 
247
273
  plan_name = f"{ras_obj.project_name}.p{current_plan_number}"
248
274
  full_plan_path = ras_obj.project_folder / plan_name
249
- logging.info(f"Constructed plan file path: {full_plan_path}")
275
+ logger.info(f"Constructed plan file path: {full_plan_path}")
250
276
  return full_plan_path
251
277
 
252
278
  @staticmethod
279
+ @log_call
253
280
  def remove_with_retry(
254
281
  path: Path,
255
282
  max_attempts: int = 5,
@@ -274,6 +301,7 @@ class RasUtils:
274
301
  >>> success = RasUtils.remove_with_retry(Path("temp_folder"), is_folder=True)
275
302
  >>> print(f"Removal successful: {success}")
276
303
  """
304
+
277
305
  ras_obj = ras_object or ras
278
306
  ras_obj.check_initialized()
279
307
 
@@ -283,32 +311,33 @@ class RasUtils:
283
311
  if path.exists():
284
312
  if is_folder:
285
313
  shutil.rmtree(path)
286
- logging.info(f"Folder removed: {path}")
314
+ logger.info(f"Folder removed: {path}")
287
315
  else:
288
316
  path.unlink()
289
- logging.info(f"File removed: {path}")
317
+ logger.info(f"File removed: {path}")
290
318
  else:
291
- logging.info(f"Path does not exist, nothing to remove: {path}")
319
+ logger.info(f"Path does not exist, nothing to remove: {path}")
292
320
  return True
293
321
  except PermissionError as pe:
294
322
  if attempt < max_attempts:
295
323
  delay = initial_delay * (2 ** (attempt - 1)) # Exponential backoff
296
- logging.warning(
324
+ logger.warning(
297
325
  f"PermissionError on attempt {attempt} to remove {path}: {pe}. "
298
326
  f"Retrying in {delay} seconds..."
299
327
  )
300
328
  time.sleep(delay)
301
329
  else:
302
- logging.error(
330
+ logger.error(
303
331
  f"Failed to remove {path} after {max_attempts} attempts due to PermissionError: {pe}. Skipping."
304
332
  )
305
333
  return False
306
334
  except Exception as e:
307
- logging.error(f"Failed to remove {path} on attempt {attempt}: {e}")
335
+ logger.exception(f"Failed to remove {path} on attempt {attempt}")
308
336
  return False
309
337
  return False
310
338
 
311
339
  @staticmethod
340
+ @log_call
312
341
  def update_plan_file(
313
342
  plan_number_or_path: Union[str, Path],
314
343
  file_type: str,
@@ -332,12 +361,13 @@ class RasUtils:
332
361
  >>> RasUtils.update_plan_file(1, "Geom", 2)
333
362
  >>> RasUtils.update_plan_file("path/to/plan.p01", "Geom", 2)
334
363
  """
364
+
335
365
  ras_obj = ras_object or ras
336
366
  ras_obj.check_initialized()
337
367
 
338
368
  valid_file_types = {'Geom': 'g', 'Flow': 'f', 'Unsteady': 'u'}
339
369
  if file_type not in valid_file_types:
340
- logging.error(
370
+ logger.error(
341
371
  f"Invalid file_type '{file_type}'. Expected one of: {', '.join(valid_file_types.keys())}"
342
372
  )
343
373
  raise ValueError(
@@ -348,7 +378,7 @@ class RasUtils:
348
378
  if not plan_file_path.is_file():
349
379
  plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_object)
350
380
  if not plan_file_path.exists():
351
- logging.error(f"Plan file not found: {plan_file_path}")
381
+ logger.error(f"Plan file not found: {plan_file_path}")
352
382
  raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
353
383
 
354
384
  file_prefix = valid_file_types[file_type]
@@ -360,30 +390,30 @@ class RasUtils:
360
390
  with plan_file_path.open('r') as file:
361
391
  lines = file.readlines()
362
392
  except Exception as e:
363
- logging.error(f"Failed to read plan file {plan_file_path}: {e}")
393
+ logger.exception(f"Failed to read plan file {plan_file_path}")
364
394
  raise
365
395
 
366
396
  updated = False
367
397
  for i, line in enumerate(lines):
368
398
  if line.startswith(search_pattern):
369
399
  lines[i] = f"{search_pattern}{file_prefix}{formatted_entry_number}\n"
370
- logging.info(
400
+ logger.info(
371
401
  f"Updated {file_type} File in {plan_file_path} to {file_prefix}{formatted_entry_number}"
372
402
  )
373
403
  updated = True
374
404
  break
375
405
 
376
406
  if not updated:
377
- logging.warning(
407
+ logger.warning(
378
408
  f"Search pattern '{search_pattern}' not found in {plan_file_path}. No update performed."
379
409
  )
380
410
 
381
411
  try:
382
412
  with plan_file_path.open('w') as file:
383
413
  file.writelines(lines)
384
- logging.info(f"Successfully updated plan file: {plan_file_path}")
414
+ logger.info(f"Successfully updated plan file: {plan_file_path}")
385
415
  except Exception as e:
386
- logging.error(f"Failed to write updates to plan file {plan_file_path}: {e}")
416
+ logger.exception(f"Failed to write updates to plan file {plan_file_path}")
387
417
  raise
388
418
 
389
419
  # Refresh RasPrj dataframes
@@ -392,12 +422,13 @@ class RasUtils:
392
422
  ras_obj.geom_df = ras_obj.get_geom_entries()
393
423
  ras_obj.flow_df = ras_obj.get_flow_entries()
394
424
  ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
395
- logging.info("RAS object dataframes have been refreshed.")
425
+ logger.info("RAS object dataframes have been refreshed.")
396
426
  except Exception as e:
397
- logging.error(f"Failed to refresh RasPrj dataframes: {e}")
427
+ logger.exception("Failed to refresh RasPrj dataframes")
398
428
  raise
399
429
 
400
430
  @staticmethod
431
+ @log_call
401
432
  def check_file_access(file_path: Path, mode: str = 'r') -> None:
402
433
  """
403
434
  Check if the file can be accessed with the specified mode.
@@ -410,36 +441,30 @@ class RasUtils:
410
441
  FileNotFoundError: If the file does not exist
411
442
  PermissionError: If the required permissions are not met
412
443
  """
444
+
413
445
  path = Path(file_path)
414
446
  if not path.exists():
415
- logging.error(f"File not found: {file_path}")
447
+ logger.error(f"File not found: {file_path}")
416
448
  raise FileNotFoundError(f"File not found: {file_path}")
417
449
 
418
450
  if mode in ('r', 'rb'):
419
451
  if not os.access(path, os.R_OK):
420
- logging.error(f"Read permission denied for file: {file_path}")
452
+ logger.error(f"Read permission denied for file: {file_path}")
421
453
  raise PermissionError(f"Read permission denied for file: {file_path}")
422
454
  else:
423
- logging.debug(f"Read access granted for file: {file_path}")
455
+ logger.debug(f"Read access granted for file: {file_path}")
424
456
 
425
457
  if mode in ('w', 'wb', 'a', 'ab'):
426
458
  parent_dir = path.parent
427
459
  if not os.access(parent_dir, os.W_OK):
428
- logging.error(f"Write permission denied for directory: {parent_dir}")
460
+ logger.error(f"Write permission denied for directory: {parent_dir}")
429
461
  raise PermissionError(f"Write permission denied for directory: {parent_dir}")
430
462
  else:
431
- logging.debug(f"Write access granted for directory: {parent_dir}")
432
-
433
-
434
-
435
-
436
- # -------------------------- Functions below were imported from funkshuns.py --------------------------
437
- # -------------------------- Converted to ras-commander style guide ----------------------------------
438
-
439
-
463
+ logger.debug(f"Write access granted for directory: {parent_dir}")
440
464
 
441
465
 
442
466
  @staticmethod
467
+ @log_call
443
468
  def convert_to_dataframe(data_source: Union[pd.DataFrame, Path], **kwargs) -> pd.DataFrame:
444
469
  """
445
470
  Converts input to a pandas DataFrame. Supports existing DataFrames or file paths (CSV, Excel, TSV, Parquet).
@@ -458,13 +483,13 @@ class RasUtils:
458
483
  >>> df = RasUtils.convert_to_dataframe(Path("data.csv"))
459
484
  >>> print(type(df))
460
485
  <class 'pandas.core.frame.DataFrame'>
461
-
462
- Attribution Note: This function is sourced from funkshuns.py by Sean Micek, and converted to the ras-commander style guide.
463
486
  """
464
487
  if isinstance(data_source, pd.DataFrame):
488
+ logger.debug("Input is already a DataFrame, returning a copy.")
465
489
  return data_source.copy()
466
490
  elif isinstance(data_source, Path):
467
491
  ext = data_source.suffix.replace('.', '', 1)
492
+ logger.info(f"Converting file with extension '{ext}' to DataFrame.")
468
493
  if ext == 'csv':
469
494
  return pd.read_csv(data_source, **kwargs)
470
495
  elif ext.startswith('x'):
@@ -474,11 +499,14 @@ class RasUtils:
474
499
  elif ext in ["parquet", "pq", "parq"]:
475
500
  return pd.read_parquet(data_source, **kwargs)
476
501
  else:
502
+ logger.error(f"Unsupported file type: {ext}")
477
503
  raise NotImplementedError(f"Unsupported file type {ext}. Should be one of csv, tsv, parquet, or xlsx.")
478
504
  else:
505
+ logger.error(f"Unsupported input type: {type(data_source)}")
479
506
  raise NotImplementedError(f"Unsupported type {type(data_source)}. Only file path / existing DataFrame supported at this time")
480
507
 
481
508
  @staticmethod
509
+ @log_call
482
510
  def save_to_excel(dataframe: pd.DataFrame, excel_path: Path, **kwargs) -> None:
483
511
  """
484
512
  Saves a pandas DataFrame to an Excel file with retry functionality.
@@ -494,8 +522,6 @@ class RasUtils:
494
522
  Example:
495
523
  >>> df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
496
524
  >>> RasUtils.save_to_excel(df, Path('output.xlsx'))
497
-
498
- Attribution Note: This function is sourced from funkshuns.py by Sean Micek, and converted to the ras-commander style guide.
499
525
  """
500
526
  saved = False
501
527
  max_attempts = 3
@@ -504,28 +530,18 @@ class RasUtils:
504
530
  while not saved and attempt < max_attempts:
505
531
  try:
506
532
  dataframe.to_excel(excel_path, **kwargs)
507
- print(f'DataFrame successfully saved to \n{excel_path}')
533
+ logger.info(f'DataFrame successfully saved to {excel_path}')
508
534
  saved = True
509
535
  except IOError as e:
510
536
  attempt += 1
511
537
  if attempt < max_attempts:
512
- print(f"Error saving file. Attempt {attempt} of {max_attempts}. Please close the Excel document if it's open.")
538
+ logger.warning(f"Error saving file. Attempt {attempt} of {max_attempts}. Please close the Excel document if it's open.")
513
539
  else:
540
+ logger.error(f"Failed to save {excel_path} after {max_attempts} attempts.")
514
541
  raise IOError(f"Failed to save {excel_path} after {max_attempts} attempts. Last error: {str(e)}")
515
542
 
516
-
517
-
518
-
519
-
520
-
521
-
522
-
523
-
524
-
525
- ##### Statistical Metrics #####
526
-
527
-
528
543
  @staticmethod
544
+ @log_call
529
545
  def calculate_rmse(observed_values: np.ndarray, predicted_values: np.ndarray, normalized: bool = True) -> float:
530
546
  """
531
547
  Calculate the Root Mean Squared Error (RMSE) between observed and predicted values.
@@ -543,17 +559,17 @@ class RasUtils:
543
559
  >>> predicted = np.array([1.1, 2.2, 2.9])
544
560
  >>> RasUtils.calculate_rmse(observed, predicted)
545
561
  0.06396394
546
-
547
- Attribution Note: This function is sourced from funkshuns.py by Sean Micek, and converted to the ras-commander style guide.
548
562
  """
549
563
  rmse = np.sqrt(np.mean((predicted_values - observed_values) ** 2))
550
564
 
551
565
  if normalized:
552
566
  rmse = rmse / np.abs(np.mean(observed_values))
553
567
 
568
+ logger.debug(f"Calculated RMSE: {rmse}")
554
569
  return rmse
555
570
 
556
571
  @staticmethod
572
+ @log_call
557
573
  def calculate_percent_bias(observed_values: np.ndarray, predicted_values: np.ndarray, as_percentage: bool = False) -> float:
558
574
  """
559
575
  Calculate the Percent Bias between observed and predicted values.
@@ -571,16 +587,16 @@ class RasUtils:
571
587
  >>> predicted = np.array([1.1, 2.2, 2.9])
572
588
  >>> RasUtils.calculate_percent_bias(observed, predicted, as_percentage=True)
573
589
  3.33333333
574
-
575
- Attribution Note: This function is sourced from funkshuns.py by Sean Micek, and converted to the ras-commander style guide.
576
590
  """
577
591
  multiplier = 100 if as_percentage else 1
578
592
 
579
593
  percent_bias = multiplier * (np.mean(predicted_values) - np.mean(observed_values)) / np.mean(observed_values)
580
594
 
595
+ logger.debug(f"Calculated Percent Bias: {percent_bias}")
581
596
  return percent_bias
582
597
 
583
598
  @staticmethod
599
+ @log_call
584
600
  def calculate_error_metrics(observed_values: np.ndarray, predicted_values: np.ndarray) -> Dict[str, float]:
585
601
  """
586
602
  Compute a trio of error metrics: correlation, RMSE, and Percent Bias.
@@ -597,14 +613,14 @@ class RasUtils:
597
613
  >>> predicted = np.array([1.1, 2.2, 2.9])
598
614
  >>> RasUtils.calculate_error_metrics(observed, predicted)
599
615
  {'cor': 0.9993, 'rmse': 0.06396, 'pb': 0.03333}
600
-
601
- Attribution Note: This function is sourced from funkshuns.py by Sean Micek, and converted to the ras-commander style guide.
602
616
  """
603
617
  correlation = np.corrcoef(observed_values, predicted_values)[0, 1]
604
618
  rmse = RasUtils.calculate_rmse(observed_values, predicted_values)
605
619
  percent_bias = RasUtils.calculate_percent_bias(observed_values, predicted_values)
606
620
 
607
- return {'cor': correlation, 'rmse': rmse, 'pb': percent_bias}
621
+ metrics = {'cor': correlation, 'rmse': rmse, 'pb': percent_bias}
622
+ logger.info(f"Calculated error metrics: {metrics}")
623
+ return metrics
608
624
 
609
625
 
610
626
 
ras_commander/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from importlib.metadata import version, PackageNotFoundError
2
+ from .logging_config import setup_logging, get_logger, log_call
2
3
 
3
4
  try:
4
5
  __version__ = version("ras-commander")
@@ -6,6 +7,9 @@ except PackageNotFoundError:
6
7
  # package is not installed
7
8
  __version__ = "unknown"
8
9
 
10
+ # Set up logging
11
+ setup_logging()
12
+
9
13
  # Import all necessary functions and classes directly
10
14
  from .RasPrj import ras, init_ras_project, get_ras_exe
11
15
  from .RasPrj import RasPrj
@@ -15,17 +19,8 @@ from .RasUnsteady import RasUnsteady
15
19
  from .RasCmdr import RasCmdr
16
20
  from .RasUtils import RasUtils
17
21
  from .RasExamples import RasExamples
18
- from .RasHdf import RasHdf # Add this line
19
-
20
- # Import all attributes from these modules
21
- from .RasPrj import *
22
- from .RasPlan import *
23
- from .RasGeo import *
24
- from .RasUnsteady import *
25
- from .RasCmdr import *
26
- from .RasUtils import *
27
- from .RasExamples import *
28
- from .RasHdf import * # Add this line
22
+ from .RasHdf import RasHdf
23
+ from .RasGpt import RasGpt
29
24
 
30
25
  # Define __all__ to specify what should be imported when using "from ras_commander import *"
31
26
  __all__ = [
@@ -39,5 +34,8 @@ __all__ = [
39
34
  "RasCmdr",
40
35
  "RasUtils",
41
36
  "RasExamples",
42
- "RasHdf" # Add this line
43
- ]
37
+ "RasHdf",
38
+ "RasGpt",
39
+ "get_logger",
40
+ "log_call"
41
+ ]