ras-commander 0.33.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 +449 -0
- ras_commander/RasExamples.py +304 -0
- ras_commander/RasGeo.py +88 -0
- ras_commander/RasPlan.py +1266 -0
- ras_commander/RasPrj.py +400 -0
- ras_commander/RasUnsteady.py +73 -0
- ras_commander/RasUtils.py +310 -0
- ras_commander/__init__.py +40 -0
- ras_commander/_version.py +16 -0
- ras_commander-0.33.0.dist-info/LICENSE +5 -0
- ras_commander-0.33.0.dist-info/METADATA +5 -0
- ras_commander-0.33.0.dist-info/RECORD +14 -0
- ras_commander-0.33.0.dist-info/WHEEL +5 -0
- ras_commander-0.33.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,310 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for the ras-commander library.
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import shutil
|
6
|
+
import logging
|
7
|
+
import time
|
8
|
+
from pathlib import Path
|
9
|
+
from .RasPrj import ras
|
10
|
+
from typing import Union
|
11
|
+
|
12
|
+
class RasUtils:
|
13
|
+
"""
|
14
|
+
A class containing utility functions for the ras-commander library.
|
15
|
+
When integrating new functions that do not clearly fit into other classes, add them here.
|
16
|
+
"""
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def create_backup(file_path: Path, backup_suffix: str = "_backup", ras_object=None) -> Path:
|
20
|
+
"""
|
21
|
+
Create a backup of the specified file.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
file_path (Path): Path to the file to be backed up
|
25
|
+
backup_suffix (str): Suffix to append to the backup file name
|
26
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
Path: Path to the created backup file
|
30
|
+
|
31
|
+
Example:
|
32
|
+
>>> backup_path = RasUtils.create_backup(Path("project.prj"))
|
33
|
+
>>> print(f"Backup created at: {backup_path}")
|
34
|
+
"""
|
35
|
+
ras_obj = ras_object or ras
|
36
|
+
ras_obj.check_initialized()
|
37
|
+
|
38
|
+
original_path = Path(file_path)
|
39
|
+
backup_path = original_path.with_name(f"{original_path.stem}{backup_suffix}{original_path.suffix}")
|
40
|
+
shutil.copy2(original_path, backup_path)
|
41
|
+
logging.info(f"Backup created: {backup_path}")
|
42
|
+
return backup_path
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def restore_from_backup(backup_path: Path, remove_backup: bool = True, ras_object=None) -> Path:
|
46
|
+
"""
|
47
|
+
Restore a file from its backup.
|
48
|
+
|
49
|
+
Parameters:
|
50
|
+
backup_path (Path): Path to the backup file
|
51
|
+
remove_backup (bool): Whether to remove the backup file after restoration
|
52
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
Path: Path to the restored file
|
56
|
+
|
57
|
+
Example:
|
58
|
+
>>> restored_path = RasUtils.restore_from_backup(Path("project_backup.prj"))
|
59
|
+
>>> print(f"File restored to: {restored_path}")
|
60
|
+
"""
|
61
|
+
ras_obj = ras_object or ras
|
62
|
+
ras_obj.check_initialized()
|
63
|
+
|
64
|
+
backup_path = Path(backup_path)
|
65
|
+
original_path = backup_path.with_name(backup_path.stem.rsplit('_backup', 1)[0] + backup_path.suffix)
|
66
|
+
shutil.copy2(backup_path, original_path)
|
67
|
+
logging.info(f"File restored: {original_path}")
|
68
|
+
if remove_backup:
|
69
|
+
backup_path.unlink()
|
70
|
+
logging.info(f"Backup removed: {backup_path}")
|
71
|
+
return original_path
|
72
|
+
|
73
|
+
@staticmethod
|
74
|
+
def create_directory(directory_path: Path, ras_object=None) -> Path:
|
75
|
+
"""
|
76
|
+
Ensure that a directory exists, creating it if necessary.
|
77
|
+
|
78
|
+
Parameters:
|
79
|
+
directory_path (Path): Path to the directory
|
80
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Path: Path to the ensured directory
|
84
|
+
|
85
|
+
Example:
|
86
|
+
>>> ensured_dir = RasUtils.create_directory(Path("output"))
|
87
|
+
>>> print(f"Directory ensured: {ensured_dir}")
|
88
|
+
"""
|
89
|
+
ras_obj = ras_object or ras
|
90
|
+
ras_obj.check_initialized()
|
91
|
+
|
92
|
+
path = Path(directory_path)
|
93
|
+
path.mkdir(parents=True, exist_ok=True)
|
94
|
+
logging.info(f"Directory ensured: {path}")
|
95
|
+
return path
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def find_files_by_extension(extension: str, ras_object=None) -> list:
|
99
|
+
"""
|
100
|
+
List all files in the project directory with a specific extension.
|
101
|
+
|
102
|
+
Parameters:
|
103
|
+
extension (str): File extension to filter (e.g., '.prj')
|
104
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
list: List of file paths matching the extension
|
108
|
+
|
109
|
+
Example:
|
110
|
+
>>> prj_files = RasUtils.find_files_by_extension('.prj')
|
111
|
+
>>> print(f"Found {len(prj_files)} .prj files")
|
112
|
+
"""
|
113
|
+
ras_obj = ras_object or ras
|
114
|
+
ras_obj.check_initialized()
|
115
|
+
|
116
|
+
files = list(ras_obj.project_folder.glob(f"*{extension}"))
|
117
|
+
return [str(file) for file in files]
|
118
|
+
|
119
|
+
@staticmethod
|
120
|
+
def get_file_size(file_path: Path, ras_object=None) -> int:
|
121
|
+
"""
|
122
|
+
Get the size of a file in bytes.
|
123
|
+
|
124
|
+
Parameters:
|
125
|
+
file_path (Path): Path to the file
|
126
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
int: Size of the file in bytes
|
130
|
+
|
131
|
+
Example:
|
132
|
+
>>> size = RasUtils.get_file_size(Path("project.prj"))
|
133
|
+
>>> print(f"File size: {size} bytes")
|
134
|
+
"""
|
135
|
+
ras_obj = ras_object or ras
|
136
|
+
ras_obj.check_initialized()
|
137
|
+
|
138
|
+
path = Path(file_path)
|
139
|
+
if path.exists():
|
140
|
+
return path.stat().st_size
|
141
|
+
else:
|
142
|
+
logging.warning(f"File not found: {path}")
|
143
|
+
return None
|
144
|
+
|
145
|
+
@staticmethod
|
146
|
+
def get_file_modification_time(file_path: Path, ras_object=None) -> float:
|
147
|
+
"""
|
148
|
+
Get the last modification time of a file.
|
149
|
+
|
150
|
+
Parameters:
|
151
|
+
file_path (Path): Path to the file
|
152
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
153
|
+
|
154
|
+
Returns:
|
155
|
+
float: Last modification time as a timestamp
|
156
|
+
|
157
|
+
Example:
|
158
|
+
>>> mtime = RasUtils.get_file_modification_time(Path("project.prj"))
|
159
|
+
>>> print(f"Last modified: {mtime}")
|
160
|
+
"""
|
161
|
+
ras_obj = ras_object or ras
|
162
|
+
ras_obj.check_initialized()
|
163
|
+
|
164
|
+
path = Path(file_path)
|
165
|
+
if path.exists():
|
166
|
+
return path.stat().st_mtime
|
167
|
+
else:
|
168
|
+
logging.warning(f"File not found: {path}")
|
169
|
+
return None
|
170
|
+
|
171
|
+
@staticmethod
|
172
|
+
def get_plan_path(current_plan_number_or_path: Union[str, Path], ras_object=None) -> Path:
|
173
|
+
"""
|
174
|
+
Get the path for a plan file with a given plan number or path.
|
175
|
+
|
176
|
+
Parameters:
|
177
|
+
current_plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
|
178
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Path: Full path to the plan file
|
182
|
+
|
183
|
+
Example:
|
184
|
+
>>> plan_path = RasUtils.get_plan_path(1)
|
185
|
+
>>> print(f"Plan file path: {plan_path}")
|
186
|
+
>>> plan_path = RasUtils.get_plan_path("path/to/plan.p01")
|
187
|
+
>>> print(f"Plan file path: {plan_path}")
|
188
|
+
"""
|
189
|
+
ras_obj = ras_object or ras
|
190
|
+
ras_obj.check_initialized()
|
191
|
+
|
192
|
+
plan_path = Path(current_plan_number_or_path)
|
193
|
+
if plan_path.is_file():
|
194
|
+
return plan_path
|
195
|
+
|
196
|
+
try:
|
197
|
+
current_plan_number = f"{int(current_plan_number_or_path):02d}" # Ensure two-digit format
|
198
|
+
except ValueError:
|
199
|
+
raise ValueError(f"Invalid plan number: {current_plan_number_or_path}. Expected a number from 1 to 99.")
|
200
|
+
|
201
|
+
plan_name = f"{ras_obj.project_name}.p{current_plan_number}"
|
202
|
+
return ras_obj.project_folder / plan_name
|
203
|
+
|
204
|
+
@staticmethod
|
205
|
+
def remove_with_retry(path: Path, max_attempts: int = 5, initial_delay: float = 1.0, is_folder: bool = True, ras_object=None) -> bool:
|
206
|
+
"""
|
207
|
+
Attempts to remove a file or folder with retry logic and exponential backoff.
|
208
|
+
|
209
|
+
Parameters:
|
210
|
+
path (Path): Path to the file or folder to be removed.
|
211
|
+
max_attempts (int): Maximum number of removal attempts.
|
212
|
+
initial_delay (float): Initial delay between attempts in seconds.
|
213
|
+
is_folder (bool): If True, the path is treated as a folder; if False, it's treated as a file.
|
214
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
215
|
+
|
216
|
+
Returns:
|
217
|
+
bool: True if the file or folder was successfully removed, False otherwise.
|
218
|
+
|
219
|
+
Example:
|
220
|
+
>>> success = RasUtils.remove_with_retry(Path("temp_folder"), is_folder=True)
|
221
|
+
>>> print(f"Removal successful: {success}")
|
222
|
+
"""
|
223
|
+
ras_obj = ras_object or ras
|
224
|
+
ras_obj.check_initialized()
|
225
|
+
|
226
|
+
path = Path(path)
|
227
|
+
for attempt in range(max_attempts):
|
228
|
+
try:
|
229
|
+
if path.exists():
|
230
|
+
if is_folder:
|
231
|
+
shutil.rmtree(path)
|
232
|
+
else:
|
233
|
+
path.unlink()
|
234
|
+
return True
|
235
|
+
except PermissionError:
|
236
|
+
if attempt < max_attempts - 1:
|
237
|
+
delay = initial_delay * (2 ** attempt) # Exponential backoff
|
238
|
+
logging.warning(f"Failed to remove {path}. Retrying in {delay} seconds...")
|
239
|
+
time.sleep(delay)
|
240
|
+
else:
|
241
|
+
logging.error(f"Failed to remove {path} after {max_attempts} attempts. Skipping.")
|
242
|
+
return False
|
243
|
+
return False
|
244
|
+
|
245
|
+
@staticmethod
|
246
|
+
def update_plan_file(plan_number_or_path: Union[str, Path], file_type: str, entry_number: int, ras_object=None) -> None:
|
247
|
+
"""
|
248
|
+
Update a plan file with a new file reference.
|
249
|
+
|
250
|
+
Parameters:
|
251
|
+
plan_number_or_path (Union[str, Path]): The plan number (1 to 99) or full path to the plan file
|
252
|
+
file_type (str): Type of file to update ('Geom', 'Flow', or 'Unsteady')
|
253
|
+
entry_number (int): Number (from 1 to 99) to set
|
254
|
+
ras_object (RasPrj, optional): RAS object to use. If None, uses the default ras object.
|
255
|
+
|
256
|
+
Raises:
|
257
|
+
ValueError: If an invalid file_type is provided
|
258
|
+
FileNotFoundError: If the plan file doesn't exist
|
259
|
+
|
260
|
+
Example:
|
261
|
+
>>> RasUtils.update_plan_file(1, "Geom", 2)
|
262
|
+
>>> RasUtils.update_plan_file("path/to/plan.p01", "Geom", 2)
|
263
|
+
"""
|
264
|
+
ras_obj = ras_object or ras
|
265
|
+
ras_obj.check_initialized()
|
266
|
+
|
267
|
+
valid_file_types = {'Geom': 'g', 'Flow': 'f', 'Unsteady': 'u'}
|
268
|
+
if file_type not in valid_file_types:
|
269
|
+
raise ValueError(f"Invalid file_type. Expected one of: {', '.join(valid_file_types.keys())}")
|
270
|
+
|
271
|
+
plan_file_path = Path(plan_number_or_path)
|
272
|
+
if not plan_file_path.is_file():
|
273
|
+
plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_object)
|
274
|
+
|
275
|
+
if not plan_file_path.exists():
|
276
|
+
raise FileNotFoundError(f"Plan file not found: {plan_file_path}")
|
277
|
+
|
278
|
+
file_prefix = valid_file_types[file_type]
|
279
|
+
search_pattern = f"{file_type} File="
|
280
|
+
entry_number = f"{int(entry_number):02d}" # Ensure two-digit format
|
281
|
+
|
282
|
+
RasUtils.check_file_access(plan_file_path, 'r')
|
283
|
+
with open(plan_file_path, 'r') as file:
|
284
|
+
lines = file.readlines()
|
285
|
+
|
286
|
+
for i, line in enumerate(lines):
|
287
|
+
if line.startswith(search_pattern):
|
288
|
+
lines[i] = f"{search_pattern}{file_prefix}{entry_number}\n"
|
289
|
+
logging.info(f"Updated {file_type} File in {plan_file_path} to {file_prefix}{entry_number}")
|
290
|
+
break
|
291
|
+
|
292
|
+
with plan_file_path.open('w') as file:
|
293
|
+
file.writelines(lines)
|
294
|
+
|
295
|
+
logging.info(f"Successfully updated plan file: {plan_file_path}")
|
296
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
297
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
298
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
299
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
300
|
+
|
301
|
+
@staticmethod
|
302
|
+
def check_file_access(file_path, mode='r'):
|
303
|
+
path = Path(file_path)
|
304
|
+
if not path.exists():
|
305
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
306
|
+
if mode in ('r', 'rb') and not os.access(path, os.R_OK):
|
307
|
+
raise PermissionError(f"Read permission denied for file: {file_path}")
|
308
|
+
if mode in ('w', 'wb', 'a', 'ab') and not os.access(path.parent, os.W_OK):
|
309
|
+
raise PermissionError(f"Write permission denied for directory: {path.parent}")
|
310
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from importlib.metadata import version, PackageNotFoundError
|
2
|
+
|
3
|
+
try:
|
4
|
+
__version__ = version("ras-commander")
|
5
|
+
except PackageNotFoundError:
|
6
|
+
# package is not installed
|
7
|
+
__version__ = "unknown"
|
8
|
+
|
9
|
+
# Import all necessary functions and classes directly
|
10
|
+
from .RasPrj import ras, init_ras_project, get_ras_exe
|
11
|
+
from .RasPrj import RasPrj
|
12
|
+
from .RasPlan import RasPlan
|
13
|
+
from .RasGeo import RasGeo
|
14
|
+
from .RasUnsteady import RasUnsteady
|
15
|
+
from .RasCmdr import RasCmdr
|
16
|
+
from .RasUtils import RasUtils
|
17
|
+
from .RasExamples import RasExamples
|
18
|
+
|
19
|
+
# Import all attributes from these modules
|
20
|
+
from .RasPrj import *
|
21
|
+
from .RasPlan import *
|
22
|
+
from .RasGeo import *
|
23
|
+
from .RasUnsteady import *
|
24
|
+
from .RasCmdr import *
|
25
|
+
from .RasUtils import *
|
26
|
+
from .RasExamples import *
|
27
|
+
|
28
|
+
# Define __all__ to specify what should be imported when using "from ras_commander import *"
|
29
|
+
__all__ = [
|
30
|
+
"ras",
|
31
|
+
"init_ras_project",
|
32
|
+
"get_ras_exe",
|
33
|
+
"RasPrj",
|
34
|
+
"RasPlan",
|
35
|
+
"RasGeo",
|
36
|
+
"RasUnsteady",
|
37
|
+
"RasCmdr",
|
38
|
+
"RasUtils",
|
39
|
+
"RasExamples"
|
40
|
+
]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# file generated by setuptools_scm
|
2
|
+
# don't change, don't track in version control
|
3
|
+
TYPE_CHECKING = False
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from typing import Tuple, Union
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
|
+
else:
|
8
|
+
VERSION_TUPLE = object
|
9
|
+
|
10
|
+
version: str
|
11
|
+
__version__: str
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
13
|
+
version_tuple: VERSION_TUPLE
|
14
|
+
|
15
|
+
__version__ = version = '0.29.dev1+g22e75d4.d20240919'
|
16
|
+
__version_tuple__ = version_tuple = (0, 29, 'dev1', 'g22e75d4.d20240919')
|
@@ -0,0 +1,5 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 William M. Katzenmeyer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
ras_commander/RasCmdr.py,sha256=ue-9RAZnv_qRvYnn6gxN0wthuYOuHuviNYxwcfkcCfE,22047
|
2
|
+
ras_commander/RasExamples.py,sha256=usNS8FNgNS2VxbUiQvRB77SC-1E7qw6pLk0Hr3pv80Y,13852
|
3
|
+
ras_commander/RasGeo.py,sha256=rQQZQEc8zn-Sur3KGcHn8Sif38eNzl5fsM3VXH_mUAI,4021
|
4
|
+
ras_commander/RasPlan.py,sha256=H0JrMsukn-qWZO0xgOkQpfQ3HtUeh0w7MwJpReDI6iw,53055
|
5
|
+
ras_commander/RasPrj.py,sha256=5RLj4jP1JYWZS-aTAxubz7_81rZV4Ie7yKO_LGC3YzI,15557
|
6
|
+
ras_commander/RasUnsteady.py,sha256=lyX_L7HD-Z1QnM-f-248a_rDvRPVlKERdsP7cdgYFqA,2995
|
7
|
+
ras_commander/RasUtils.py,sha256=HZK5n_6eK8hC_BPqB6u3nu1smmFQ95ft7oPJ9wNltVI,12054
|
8
|
+
ras_commander/__init__.py,sha256=uY-IIzmE36WvC1IwQR1OSvll0SY7COcb7rhVw6uvuoA,1050
|
9
|
+
ras_commander/_version.py,sha256=BReLomJ164W3bJhfQJi0gbNKc3DXCzwusmCheUzClB8,478
|
10
|
+
ras_commander-0.33.0.dist-info/LICENSE,sha256=_pbd6qHnlsz1iQ-ozDW_49r86BZT6CRwO2iBtw0iN6M,457
|
11
|
+
ras_commander-0.33.0.dist-info/METADATA,sha256=nuqhc4qkPQZ07RoyYMN6-TlrR4FjDbYQu5HW3lJQrZY,86
|
12
|
+
ras_commander-0.33.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
13
|
+
ras_commander-0.33.0.dist-info/top_level.txt,sha256=i76S7eKLFC8doKcXDl3aiOr9RwT06G8adI6YuKbQDaA,14
|
14
|
+
ras_commander-0.33.0.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
ras_commander
|