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/RasCmdr.py +360 -332
- ras_commander/RasExamples.py +113 -80
- ras_commander/RasGeo.py +38 -28
- ras_commander/RasGpt.py +142 -0
- ras_commander/RasHdf.py +170 -253
- ras_commander/RasPlan.py +115 -166
- ras_commander/RasPrj.py +212 -141
- ras_commander/RasUnsteady.py +37 -22
- ras_commander/RasUtils.py +98 -82
- ras_commander/__init__.py +11 -13
- ras_commander/logging_config.py +80 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/METADATA +22 -19
- ras_commander-0.37.0.dist-info/RECORD +17 -0
- ras_commander-0.35.0.dist-info/RECORD +0 -15
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.35.0.dist-info → ras_commander-0.37.0.dist-info}/top_level.txt +0 -0
ras_commander/RasExamples.py
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
"""
|
2
|
+
RasExamples - Manage and load HEC-RAS example projects for testing and development
|
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
|
+
"""
|
1
26
|
import os
|
2
27
|
import requests
|
3
28
|
import zipfile
|
@@ -10,15 +35,10 @@ from datetime import datetime
|
|
10
35
|
import logging
|
11
36
|
import re
|
12
37
|
from tqdm import tqdm
|
38
|
+
from ras_commander import get_logger
|
39
|
+
from ras_commander.logging_config import log_call
|
13
40
|
|
14
|
-
|
15
|
-
logging.basicConfig(
|
16
|
-
level=logging.INFO, # Set the logging level to INFO
|
17
|
-
format='%(asctime)s - %(levelname)s - %(message)s', # Log message format
|
18
|
-
handlers=[
|
19
|
-
logging.StreamHandler() # Log to stderr
|
20
|
-
]
|
21
|
-
)
|
41
|
+
logger = get_logger(__name__)
|
22
42
|
|
23
43
|
class RasExamples:
|
24
44
|
"""
|
@@ -28,10 +48,8 @@ class RasExamples:
|
|
28
48
|
It supports both default HEC-RAS example projects and custom projects from user-provided URLs.
|
29
49
|
Additionally, it includes functionality to download FEMA's Base Level Engineering (BLE) models
|
30
50
|
from CSV files provided by the FEMA Estimated Base Flood Elevation (BFE) Viewer.
|
31
|
-
|
32
|
-
[Documentation as previously provided]
|
33
51
|
"""
|
34
|
-
|
52
|
+
@log_call
|
35
53
|
def __init__(self):
|
36
54
|
"""
|
37
55
|
Initialize the RasExamples class.
|
@@ -50,9 +68,10 @@ class RasExamples:
|
|
50
68
|
self.csv_file_path = self.examples_dir / 'example_projects.csv'
|
51
69
|
|
52
70
|
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
53
|
-
|
71
|
+
logger.info(f"Example projects folder: {self.projects_dir}")
|
54
72
|
self._load_project_data()
|
55
73
|
|
74
|
+
@log_call
|
56
75
|
def _load_project_data(self):
|
57
76
|
"""
|
58
77
|
Load project data from CSV if up-to-date, otherwise extract from zip.
|
@@ -60,43 +79,45 @@ class RasExamples:
|
|
60
79
|
self._find_zip_file()
|
61
80
|
|
62
81
|
if not self.zip_file_path:
|
63
|
-
|
82
|
+
logger.info("No example projects zip file found. Downloading...")
|
64
83
|
self.get_example_projects()
|
65
84
|
|
66
85
|
try:
|
67
86
|
zip_modified_time = os.path.getmtime(self.zip_file_path)
|
68
87
|
except FileNotFoundError:
|
69
|
-
|
88
|
+
logger.error(f"Zip file not found at {self.zip_file_path}.")
|
70
89
|
return
|
71
90
|
|
72
91
|
if self.csv_file_path.exists():
|
73
92
|
csv_modified_time = os.path.getmtime(self.csv_file_path)
|
74
93
|
|
75
94
|
if csv_modified_time >= zip_modified_time:
|
76
|
-
|
95
|
+
logger.info("Loading project data from CSV...")
|
77
96
|
try:
|
78
97
|
self.folder_df = pd.read_csv(self.csv_file_path)
|
79
|
-
|
98
|
+
logger.info(f"Loaded {len(self.folder_df)} projects from CSV. Use list_categories() and list_projects() to explore them.")
|
80
99
|
except Exception as e:
|
81
|
-
|
100
|
+
logger.error(f"Failed to read CSV file: {e}")
|
82
101
|
self.folder_df = None
|
83
102
|
return
|
84
103
|
|
85
|
-
|
104
|
+
logger.info("Extracting folder structure from zip file...")
|
86
105
|
self._extract_folder_structure()
|
87
106
|
self._save_to_csv()
|
88
107
|
|
108
|
+
@log_call
|
89
109
|
def _find_zip_file(self):
|
90
110
|
"""Locate the example projects zip file in the examples directory."""
|
91
111
|
for version in self.valid_versions:
|
92
112
|
potential_zip = self.examples_dir / f"Example_Projects_{version.replace('.', '_')}.zip"
|
93
113
|
if potential_zip.exists():
|
94
114
|
self.zip_file_path = potential_zip
|
95
|
-
|
115
|
+
logger.info(f"Found zip file: {self.zip_file_path}")
|
96
116
|
break
|
97
117
|
else:
|
98
|
-
|
118
|
+
logger.warning("No existing example projects zip file found.")
|
99
119
|
|
120
|
+
@log_call
|
100
121
|
def _extract_folder_structure(self):
|
101
122
|
"""
|
102
123
|
Extract folder structure from the zip file.
|
@@ -115,34 +136,36 @@ class RasExamples:
|
|
115
136
|
})
|
116
137
|
|
117
138
|
self.folder_df = pd.DataFrame(folder_data).drop_duplicates()
|
118
|
-
|
119
|
-
|
139
|
+
logger.info(f"Extracted {len(self.folder_df)} projects.")
|
140
|
+
logger.debug(f"folder_df:\n{self.folder_df}")
|
120
141
|
except zipfile.BadZipFile:
|
121
|
-
|
142
|
+
logger.error(f"The file {self.zip_file_path} is not a valid zip file.")
|
122
143
|
self.folder_df = pd.DataFrame(columns=['Category', 'Project'])
|
123
144
|
except Exception as e:
|
124
|
-
|
145
|
+
logger.error(f"An error occurred while extracting the folder structure: {str(e)}")
|
125
146
|
self.folder_df = pd.DataFrame(columns=['Category', 'Project'])
|
126
147
|
|
148
|
+
@log_call
|
127
149
|
def _save_to_csv(self):
|
128
150
|
"""Save the extracted folder structure to CSV file."""
|
129
151
|
if self.folder_df is not None and not self.folder_df.empty:
|
130
152
|
try:
|
131
153
|
self.folder_df.to_csv(self.csv_file_path, index=False)
|
132
|
-
|
154
|
+
logger.info(f"Saved project data to {self.csv_file_path}")
|
133
155
|
except Exception as e:
|
134
|
-
|
156
|
+
logger.error(f"Failed to save project data to CSV: {e}")
|
135
157
|
else:
|
136
|
-
|
158
|
+
logger.warning("No folder data to save to CSV.")
|
137
159
|
|
160
|
+
@log_call
|
138
161
|
def get_example_projects(self, version_number='6.5'):
|
139
162
|
"""
|
140
163
|
Download and extract HEC-RAS example projects for a specified version.
|
141
164
|
"""
|
142
|
-
|
165
|
+
logger.info(f"Getting example projects for version {version_number}")
|
143
166
|
if version_number not in self.valid_versions:
|
144
167
|
error_msg = f"Invalid version number. Valid versions are: {', '.join(self.valid_versions)}"
|
145
|
-
|
168
|
+
logger.error(error_msg)
|
146
169
|
raise ValueError(error_msg)
|
147
170
|
|
148
171
|
zip_url = f"{self.base_url}1.0.31/Example_Projects_{version_number.replace('.', '_')}.zip"
|
@@ -152,48 +175,51 @@ class RasExamples:
|
|
152
175
|
self.zip_file_path = self.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"
|
153
176
|
|
154
177
|
if not self.zip_file_path.exists():
|
155
|
-
|
178
|
+
logger.info(f"Downloading HEC-RAS Example Projects from {zip_url}. \nThe file is over 400 MB, so it may take a few minutes to download....")
|
156
179
|
try:
|
157
180
|
response = requests.get(zip_url, stream=True)
|
158
181
|
response.raise_for_status()
|
159
182
|
with open(self.zip_file_path, 'wb') as file:
|
160
183
|
shutil.copyfileobj(response.raw, file)
|
161
|
-
|
184
|
+
logger.info(f"Downloaded to {self.zip_file_path}")
|
162
185
|
except requests.exceptions.RequestException as e:
|
163
|
-
|
186
|
+
logger.error(f"Failed to download the zip file: {e}")
|
164
187
|
raise
|
165
188
|
else:
|
166
|
-
|
189
|
+
logger.info("HEC-RAS Example Projects zip file already exists. Skipping download.")
|
167
190
|
|
168
191
|
self._load_project_data()
|
169
192
|
return self.projects_dir
|
170
193
|
|
194
|
+
@log_call
|
171
195
|
def list_categories(self):
|
172
196
|
"""
|
173
197
|
List all categories of example projects.
|
174
198
|
"""
|
175
199
|
if self.folder_df is None or 'Category' not in self.folder_df.columns:
|
176
|
-
|
200
|
+
logger.warning("No categories available. Make sure the zip file is properly loaded.")
|
177
201
|
return []
|
178
202
|
categories = self.folder_df['Category'].unique()
|
179
|
-
|
203
|
+
logger.info(f"Available categories: {', '.join(categories)}")
|
180
204
|
return categories.tolist()
|
181
205
|
|
206
|
+
@log_call
|
182
207
|
def list_projects(self, category=None):
|
183
208
|
"""
|
184
209
|
List all projects or projects in a specific category.
|
185
210
|
"""
|
186
211
|
if self.folder_df is None:
|
187
|
-
|
212
|
+
logger.warning("No projects available. Make sure the zip file is properly loaded.")
|
188
213
|
return []
|
189
214
|
if category:
|
190
215
|
projects = self.folder_df[self.folder_df['Category'] == category]['Project'].unique()
|
191
|
-
|
216
|
+
logger.info(f"Projects in category '{category}': {', '.join(projects)}")
|
192
217
|
else:
|
193
218
|
projects = self.folder_df['Project'].unique()
|
194
|
-
|
219
|
+
logger.info(f"All available projects: {', '.join(projects)}")
|
195
220
|
return projects.tolist()
|
196
221
|
|
222
|
+
@log_call
|
197
223
|
def extract_project(self, project_names: Union[str, List[str]]):
|
198
224
|
"""
|
199
225
|
Extract one or more specific HEC-RAS projects from the zip file.
|
@@ -204,28 +230,28 @@ class RasExamples:
|
|
204
230
|
extracted_paths = []
|
205
231
|
|
206
232
|
for project_name in project_names:
|
207
|
-
|
208
|
-
|
233
|
+
logger.info("----- RasExamples Extracting Project -----")
|
234
|
+
logger.info(f"Extracting project '{project_name}'")
|
209
235
|
project_path = self.projects_dir / project_name
|
210
236
|
|
211
237
|
if project_path.exists():
|
212
|
-
|
238
|
+
logger.info(f"Project '{project_name}' already exists. Deleting existing folder...")
|
213
239
|
try:
|
214
240
|
shutil.rmtree(project_path)
|
215
|
-
|
241
|
+
logger.info(f"Existing folder for project '{project_name}' has been deleted.")
|
216
242
|
except Exception as e:
|
217
|
-
|
243
|
+
logger.error(f"Failed to delete existing project folder '{project_name}': {e}")
|
218
244
|
continue
|
219
245
|
|
220
246
|
if self.folder_df is None or self.folder_df.empty:
|
221
247
|
error_msg = "No project information available. Make sure the zip file is properly loaded."
|
222
|
-
|
248
|
+
logger.error(error_msg)
|
223
249
|
raise ValueError(error_msg)
|
224
250
|
|
225
251
|
project_info = self.folder_df[self.folder_df['Project'] == project_name]
|
226
252
|
if project_info.empty:
|
227
253
|
error_msg = f"Project '{project_name}' not found in the zip file."
|
228
|
-
|
254
|
+
logger.error(error_msg)
|
229
255
|
raise ValueError(error_msg)
|
230
256
|
|
231
257
|
category = project_info['Category'].iloc[0]
|
@@ -248,40 +274,44 @@ class RasExamples:
|
|
248
274
|
with zip_ref.open(file) as source, open(extract_path, "wb") as target:
|
249
275
|
shutil.copyfileobj(source, target)
|
250
276
|
|
251
|
-
|
277
|
+
logger.info(f"Successfully extracted project '{project_name}' to {project_path}")
|
252
278
|
extracted_paths.append(project_path)
|
253
279
|
except zipfile.BadZipFile:
|
254
|
-
|
280
|
+
logger.error(f"Error: The file {self.zip_file_path} is not a valid zip file.")
|
255
281
|
except FileNotFoundError:
|
256
|
-
|
282
|
+
logger.error(f"Error: The file {self.zip_file_path} was not found.")
|
257
283
|
except Exception as e:
|
258
|
-
|
259
|
-
|
284
|
+
logger.error(f"An unexpected error occurred while extracting the project: {str(e)}")
|
285
|
+
logger.info("----- RasExamples Extraction Complete -----")
|
260
286
|
return extracted_paths
|
261
287
|
|
288
|
+
@log_call
|
262
289
|
def is_project_extracted(self, project_name):
|
263
290
|
"""
|
264
291
|
Check if a specific project is already extracted.
|
265
292
|
"""
|
266
293
|
project_path = self.projects_dir / project_name
|
267
294
|
is_extracted = project_path.exists()
|
268
|
-
|
295
|
+
logger.info(f"Project '{project_name}' extracted: {is_extracted}")
|
269
296
|
return is_extracted
|
270
297
|
|
298
|
+
@log_call
|
271
299
|
def clean_projects_directory(self):
|
272
300
|
"""Remove all extracted projects from the example_projects directory."""
|
273
|
-
|
301
|
+
logger.info(f"Cleaning projects directory: {self.projects_dir}")
|
274
302
|
if self.projects_dir.exists():
|
275
303
|
try:
|
276
304
|
shutil.rmtree(self.projects_dir)
|
277
|
-
|
305
|
+
logger.info("All projects have been removed.")
|
278
306
|
except Exception as e:
|
279
|
-
|
307
|
+
logger.error(f"Failed to remove projects directory: {e}")
|
280
308
|
else:
|
281
|
-
|
309
|
+
logger.warning("Projects directory does not exist.")
|
282
310
|
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
283
|
-
|
284
|
-
|
311
|
+
logger.info("Projects directory cleaned and recreated.")
|
312
|
+
|
313
|
+
|
314
|
+
@log_call
|
285
315
|
def download_fema_ble_model(self, csv_file: Union[str, Path], output_base_dir: Union[str, Path] = None):
|
286
316
|
"""
|
287
317
|
Download a single FEMA Base Level Engineering (BLE) model from a CSV file and organize it into folders.
|
@@ -316,37 +346,37 @@ class RasExamples:
|
|
316
346
|
output_base_dir = Path(output_base_dir)
|
317
347
|
|
318
348
|
if not csv_file.exists() or not csv_file.is_file():
|
319
|
-
|
349
|
+
logger.error(f"The specified CSV file does not exist: {csv_file}")
|
320
350
|
raise FileNotFoundError(f"The specified CSV file does not exist: {csv_file}")
|
321
351
|
|
322
352
|
output_base_dir.mkdir(parents=True, exist_ok=True)
|
323
|
-
|
353
|
+
logger.info(f"BLE model will be organized in: {output_base_dir}")
|
324
354
|
|
325
355
|
try:
|
326
356
|
# Extract region name from the filename (assuming format <AnyCharacters>_<Region>_DownloadIndex.csv)
|
327
357
|
match = re.match(r'.+?_(.+?)_DownloadIndex\.csv', csv_file.name)
|
328
358
|
if not match:
|
329
|
-
|
359
|
+
logger.warning(f"Filename does not match expected pattern and will be skipped: {csv_file.name}")
|
330
360
|
return
|
331
361
|
region = match.group(1)
|
332
|
-
|
362
|
+
logger.info(f"Processing region: {region}")
|
333
363
|
|
334
364
|
# Create folder for this region
|
335
365
|
region_folder = output_base_dir / region
|
336
366
|
region_folder.mkdir(parents=True, exist_ok=True)
|
337
|
-
|
367
|
+
logger.info(f"Created/verified region folder: {region_folder}")
|
338
368
|
|
339
369
|
# Read the CSV file
|
340
370
|
try:
|
341
371
|
df = pd.read_csv(csv_file, comment='#')
|
342
372
|
except pd.errors.ParserError as e:
|
343
|
-
|
373
|
+
logger.error(f"Error parsing CSV file {csv_file.name}: {e}")
|
344
374
|
return
|
345
375
|
|
346
376
|
# Verify required columns exist
|
347
377
|
required_columns = {'URL', 'FileName', 'FileSize', 'Description', 'Details'}
|
348
378
|
if not required_columns.issubset(df.columns):
|
349
|
-
|
379
|
+
logger.warning(f"CSV file {csv_file.name} is missing required columns and will be skipped.")
|
350
380
|
return
|
351
381
|
|
352
382
|
# Process each row in the CSV
|
@@ -360,7 +390,7 @@ class RasExamples:
|
|
360
390
|
try:
|
361
391
|
file_size = self._convert_size_to_bytes(file_size_str)
|
362
392
|
except ValueError as e:
|
363
|
-
|
393
|
+
logger.error(f"Error converting file size '{file_size_str}' to bytes: {e}")
|
364
394
|
continue
|
365
395
|
|
366
396
|
# Create a subfolder based on the safe description name
|
@@ -372,43 +402,45 @@ class RasExamples:
|
|
372
402
|
downloaded_file = csv_folder / file_name
|
373
403
|
if not downloaded_file.exists():
|
374
404
|
try:
|
375
|
-
|
405
|
+
logger.info(f"Downloading {file_name} from {download_url} to {csv_folder}")
|
376
406
|
downloaded_file = self._download_file_with_progress(download_url, csv_folder, file_size)
|
377
|
-
|
407
|
+
logger.info(f"Downloaded file to: {downloaded_file}")
|
378
408
|
except Exception as e:
|
379
|
-
|
409
|
+
logger.error(f"Failed to download {download_url}: {e}")
|
380
410
|
continue
|
381
411
|
else:
|
382
|
-
|
412
|
+
logger.info(f"File {file_name} already exists in {csv_folder}, skipping download.")
|
383
413
|
|
384
414
|
# If it's a zip file, unzip it to the description folder
|
385
415
|
if downloaded_file.suffix == '.zip':
|
386
416
|
# If the folder exists, delete it
|
387
417
|
if description_folder.exists():
|
388
|
-
|
418
|
+
logger.info(f"Folder {description_folder} already exists. Deleting it.")
|
389
419
|
shutil.rmtree(description_folder)
|
390
420
|
|
391
421
|
description_folder.mkdir(parents=True, exist_ok=True)
|
392
|
-
|
422
|
+
logger.info(f"Created/verified description folder: {description_folder}")
|
393
423
|
|
394
|
-
|
424
|
+
logger.info(f"Unzipping {downloaded_file} into {description_folder}")
|
395
425
|
try:
|
396
426
|
with zipfile.ZipFile(downloaded_file, 'r') as zip_ref:
|
397
427
|
zip_ref.extractall(description_folder)
|
398
|
-
|
428
|
+
logger.info(f"Unzipped {downloaded_file} successfully.")
|
399
429
|
except Exception as e:
|
400
|
-
|
430
|
+
logger.error(f"Failed to extract {downloaded_file}: {e}")
|
401
431
|
except Exception as e:
|
402
|
-
|
432
|
+
logger.error(f"An error occurred while processing {csv_file.name}: {e}")
|
403
433
|
|
434
|
+
@log_call
|
404
435
|
def _make_safe_folder_name(self, name: str) -> str:
|
405
436
|
"""
|
406
437
|
Convert a string to a safe folder name by replacing unsafe characters with underscores.
|
407
438
|
"""
|
408
439
|
safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '_', name)
|
409
|
-
|
440
|
+
logger.debug(f"Converted '{name}' to safe folder name '{safe_name}'")
|
410
441
|
return safe_name
|
411
442
|
|
443
|
+
@log_call
|
412
444
|
def _download_file_with_progress(self, url: str, dest_folder: Path, file_size: int) -> Path:
|
413
445
|
"""
|
414
446
|
Download a file from a URL to a specified destination folder with progress bar.
|
@@ -427,15 +459,16 @@ class RasExamples:
|
|
427
459
|
for chunk in r.iter_content(chunk_size=8192):
|
428
460
|
size = f.write(chunk)
|
429
461
|
progress_bar.update(size)
|
430
|
-
|
462
|
+
logger.info(f"Successfully downloaded {url} to {local_filename}")
|
431
463
|
return local_filename
|
432
464
|
except requests.exceptions.RequestException as e:
|
433
|
-
|
465
|
+
logger.error(f"Request failed for {url}: {e}")
|
434
466
|
raise
|
435
467
|
except Exception as e:
|
436
|
-
|
468
|
+
logger.error(f"Failed to write file {local_filename}: {e}")
|
437
469
|
raise
|
438
470
|
|
471
|
+
@log_call
|
439
472
|
def _convert_size_to_bytes(self, size_str: str) -> int:
|
440
473
|
"""
|
441
474
|
Convert a human-readable file size to bytes.
|
@@ -453,7 +486,7 @@ class RasExamples:
|
|
453
486
|
# ras_examples.download_fema_ble_models('/path/to/csv/files', '/path/to/output/folder')
|
454
487
|
# extracted_paths = ras_examples.extract_project(["Bald Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])
|
455
488
|
# for path in extracted_paths:
|
456
|
-
#
|
489
|
+
# logger.info(f"Extracted to: {path}")
|
457
490
|
|
458
491
|
|
459
492
|
"""
|
ras_commander/RasGeo.py
CHANGED
@@ -1,21 +1,37 @@
|
|
1
1
|
"""
|
2
|
-
Operations for handling geometry files in HEC-RAS projects
|
2
|
+
RasGeo - Operations for handling geometry 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
|
3
25
|
"""
|
26
|
+
import os
|
4
27
|
from pathlib import Path
|
5
28
|
from typing import List, Union
|
6
29
|
from .RasPlan import RasPlan
|
7
30
|
from .RasPrj import ras
|
8
|
-
import
|
9
|
-
import
|
31
|
+
from ras_commander import get_logger
|
32
|
+
from ras_commander.logging_config import log_call
|
10
33
|
|
11
|
-
|
12
|
-
logging.basicConfig(
|
13
|
-
level=logging.INFO,
|
14
|
-
format='%(asctime)s - %(levelname)s - %(message)s',
|
15
|
-
# You can add a filename parameter here to log to a file
|
16
|
-
# filename='rasgeo.log',
|
17
|
-
# Uncomment the above line to enable file logging
|
18
|
-
)
|
34
|
+
logger = get_logger(__name__)
|
19
35
|
|
20
36
|
class RasGeo:
|
21
37
|
"""
|
@@ -23,6 +39,7 @@ class RasGeo:
|
|
23
39
|
"""
|
24
40
|
|
25
41
|
@staticmethod
|
42
|
+
@log_call
|
26
43
|
def clear_geompre_files(
|
27
44
|
plan_files: Union[str, Path, List[Union[str, Path]]] = None,
|
28
45
|
ras_object = None
|
@@ -58,11 +75,6 @@ class RasGeo:
|
|
58
75
|
Note:
|
59
76
|
This function updates the ras object's geometry dataframe after clearing the preprocessor files.
|
60
77
|
"""
|
61
|
-
## Explicit Function Steps
|
62
|
-
# 1. Initialize the ras_object, defaulting to the global ras if not provided.
|
63
|
-
# 2. Define a helper function to clear a single geometry preprocessor file.
|
64
|
-
# 3. Determine the list of plan files to process based on the input.
|
65
|
-
# 4. Iterate over each plan file and clear its geometry preprocessor file.
|
66
78
|
ras_obj = ras_object or ras
|
67
79
|
ras_obj.check_initialized()
|
68
80
|
|
@@ -72,38 +84,36 @@ class RasGeo:
|
|
72
84
|
geom_preprocessor_file = plan_path.with_suffix(geom_preprocessor_suffix)
|
73
85
|
if geom_preprocessor_file.exists():
|
74
86
|
try:
|
75
|
-
logging.info(f"Deleting geometry preprocessor file: {geom_preprocessor_file}")
|
76
87
|
geom_preprocessor_file.unlink()
|
77
|
-
|
88
|
+
logger.info(f"Deleted geometry preprocessor file: {geom_preprocessor_file}")
|
78
89
|
except PermissionError:
|
79
|
-
|
90
|
+
logger.error(f"Permission denied: Unable to delete geometry preprocessor file: {geom_preprocessor_file}")
|
80
91
|
raise PermissionError(f"Unable to delete geometry preprocessor file: {geom_preprocessor_file}. Permission denied.")
|
81
92
|
except OSError as e:
|
82
|
-
|
93
|
+
logger.error(f"Error deleting geometry preprocessor file: {geom_preprocessor_file}. {str(e)}")
|
83
94
|
raise OSError(f"Error deleting geometry preprocessor file: {geom_preprocessor_file}. {str(e)}")
|
84
95
|
else:
|
85
|
-
|
96
|
+
logger.warning(f"No geometry preprocessor file found for: {plan_file}")
|
86
97
|
|
87
98
|
if plan_files is None:
|
88
|
-
|
99
|
+
logger.info("Clearing all geometry preprocessor files in the project directory.")
|
89
100
|
plan_files_to_clear = list(ras_obj.project_folder.glob(r'*.p*'))
|
90
101
|
elif isinstance(plan_files, (str, Path)):
|
91
102
|
plan_files_to_clear = [plan_files]
|
92
|
-
|
103
|
+
logger.info(f"Clearing geometry preprocessor file for single plan: {plan_files}")
|
93
104
|
elif isinstance(plan_files, list):
|
94
105
|
plan_files_to_clear = plan_files
|
95
|
-
|
106
|
+
logger.info(f"Clearing geometry preprocessor files for multiple plans: {plan_files}")
|
96
107
|
else:
|
97
|
-
|
108
|
+
logger.error("Invalid input type for plan_files.")
|
98
109
|
raise ValueError("Invalid input. Please provide a string, Path, list of paths, or None.")
|
99
110
|
|
100
111
|
for plan_file in plan_files_to_clear:
|
101
112
|
clear_single_file(plan_file, ras_obj)
|
102
113
|
|
103
|
-
# Update the geometry dataframe
|
104
114
|
try:
|
105
115
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
106
|
-
|
116
|
+
logger.info("Geometry dataframe updated successfully.")
|
107
117
|
except Exception as e:
|
108
|
-
|
118
|
+
logger.error(f"Failed to update geometry dataframe: {str(e)}")
|
109
119
|
raise
|