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.
@@ -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,5 @@
1
+ Metadata-Version: 2.1
2
+ Name: ras-commander
3
+ Version: 0.33.0
4
+ License-File: LICENSE
5
+
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ ras_commander