rpa-suite 1.6.2__py3-none-any.whl → 1.6.4__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.
- rpa_suite/__init__.py +1 -1
- rpa_suite/core/__init__.py +5 -1
- rpa_suite/core/artemis.py +445 -0
- rpa_suite/core/asyncrun.py +15 -8
- rpa_suite/core/browser.py +44 -41
- rpa_suite/core/clock.py +46 -13
- rpa_suite/core/date.py +41 -16
- rpa_suite/core/dir.py +29 -72
- rpa_suite/core/email.py +26 -15
- rpa_suite/core/file.py +46 -43
- rpa_suite/core/iris.py +87 -79
- rpa_suite/core/log.py +134 -46
- rpa_suite/core/parallel.py +185 -182
- rpa_suite/core/print.py +119 -96
- rpa_suite/core/regex.py +26 -26
- rpa_suite/core/validate.py +20 -76
- rpa_suite/functions/__init__.py +1 -1
- rpa_suite/suite.py +13 -1
- rpa_suite/utils/__init__.py +1 -1
- rpa_suite/utils/system.py +64 -61
- {rpa_suite-1.6.2.dist-info → rpa_suite-1.6.4.dist-info}/METADATA +5 -17
- rpa_suite-1.6.4.dist-info/RECORD +27 -0
- rpa_suite-1.6.2.dist-info/RECORD +0 -26
- {rpa_suite-1.6.2.dist-info → rpa_suite-1.6.4.dist-info}/WHEEL +0 -0
- {rpa_suite-1.6.2.dist-info → rpa_suite-1.6.4.dist-info}/licenses/LICENSE +0 -0
- {rpa_suite-1.6.2.dist-info → rpa_suite-1.6.4.dist-info}/top_level.txt +0 -0
rpa_suite/core/parallel.py
CHANGED
@@ -6,109 +6,95 @@ from typing import Any, Callable, Dict, Optional, TypeVar, Generic
|
|
6
6
|
import time
|
7
7
|
import traceback
|
8
8
|
|
9
|
+
from rpa_suite.functions._printer import error_print, alert_print, success_print
|
10
|
+
|
9
11
|
# Define a generic type for the function return
|
10
12
|
T = TypeVar("T")
|
11
13
|
|
14
|
+
class ParallelRunnerError(Exception):
|
15
|
+
"""Custom exception for ParallelRunner errors."""
|
16
|
+
def __init__(self, message):
|
17
|
+
clean_message = message.replace("ParallelRunner Error:", "").strip()
|
18
|
+
super().__init__(f'ParallelRunner Error: {clean_message}')
|
12
19
|
|
13
20
|
class ParallelRunner(Generic[T]):
|
14
21
|
"""
|
15
22
|
Class to execute functions in parallel while maintaining the main application flow.
|
16
23
|
|
17
24
|
Allows starting a function in a separate process and retrieving its result later.
|
18
|
-
|
19
|
-
pt-br
|
20
|
-
------
|
21
|
-
Classe para executar funções em paralelo mantendo o fluxo principal da aplicação.
|
22
|
-
|
23
|
-
Permite iniciar uma função em um processo separado e obter seu resultado posteriormente.
|
24
25
|
"""
|
25
26
|
|
26
27
|
display_message = None
|
27
28
|
|
28
|
-
def __init__(self, display_message: bool = False):
|
29
|
+
def __init__(self, display_message: bool = False) -> None:
|
29
30
|
"""
|
30
31
|
Initializes the ParallelRunner.
|
31
32
|
|
32
33
|
Args:
|
33
34
|
display_message (bool): If True, displays debug messages during execution.
|
34
|
-
|
35
|
-
pt-br
|
36
|
-
------
|
37
|
-
Inicializa o ParallelRunner.
|
38
|
-
|
39
|
-
Args:
|
40
|
-
display_message (bool): Se True, exibe mensagens de debug durante a execução.
|
41
35
|
"""
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
try:
|
37
|
+
self._manager = Manager()
|
38
|
+
self._result_dict = self._manager.dict()
|
39
|
+
self._process = None
|
40
|
+
self._start_time = None
|
41
|
+
self.display_message = display_message
|
42
|
+
|
43
|
+
if self.display_message:
|
44
|
+
success_print("ParallelRunner initialized successfully")
|
45
|
+
|
46
|
+
except Exception as e:
|
47
|
+
raise ParallelRunnerError(f"Error initializing ParallelRunner: {str(e)}") from e
|
48
48
|
|
49
49
|
@staticmethod
|
50
50
|
def _execute_function(function, args, kwargs, result_dict):
|
51
51
|
"""
|
52
52
|
Static method that executes the target function and stores the result.
|
53
53
|
This function needs to be defined at the module level to be "picklable".
|
54
|
-
|
55
|
-
pt-br
|
56
|
-
------
|
57
|
-
Função estática que executa a função alvo e armazena o resultado.
|
58
|
-
Esta função precisa ser definida no nível do módulo para ser "picklable".
|
59
54
|
"""
|
60
55
|
try:
|
61
|
-
#
|
56
|
+
# Execute the user function with the provided arguments
|
62
57
|
result = function(*args, **kwargs)
|
63
58
|
|
64
|
-
#
|
59
|
+
# Store the result in the shared dictionary
|
65
60
|
result_dict["status"] = "success"
|
66
61
|
result_dict["result"] = result
|
67
62
|
|
68
|
-
# Para debug
|
69
|
-
# print(f"[Processo Filho] Resultado calculado: {result}")
|
70
|
-
# print(f"[Processo Filho] Dicionário de resultados: {dict(result_dict)}")
|
71
|
-
|
72
63
|
except Exception as e:
|
73
|
-
#
|
64
|
+
# In case of error, store information about the error
|
74
65
|
result_dict["status"] = "error"
|
75
66
|
result_dict["error"] = str(e)
|
76
67
|
result_dict["traceback"] = traceback.format_exc()
|
77
68
|
|
78
|
-
#
|
79
|
-
|
69
|
+
# For debug
|
70
|
+
error_print(f"[Child Process] Error occurred: {str(e)}")
|
80
71
|
|
81
72
|
@staticmethod
|
82
73
|
def _execute_function_w_disp_msg(function, args, kwargs, result_dict):
|
83
74
|
"""
|
84
75
|
Static method that executes the target function and stores the result.
|
85
76
|
This function needs to be defined at the module level to be "picklable".
|
86
|
-
|
87
|
-
pt-br
|
88
|
-
------
|
89
|
-
Função estática que executa a função alvo e armazena o resultado.
|
90
|
-
Esta função precisa ser definida no nível do módulo para ser "picklable".
|
91
77
|
"""
|
92
78
|
try:
|
93
|
-
#
|
79
|
+
# Execute the user function with the provided arguments
|
94
80
|
result = function(*args, **kwargs)
|
95
81
|
|
96
|
-
#
|
82
|
+
# Store the result in the shared dictionary
|
97
83
|
result_dict["status"] = "success"
|
98
84
|
result_dict["result"] = result
|
99
85
|
|
100
|
-
#
|
101
|
-
|
102
|
-
|
86
|
+
# For debug
|
87
|
+
success_print(f"[Child Process] Result calculated: {result}")
|
88
|
+
success_print(f"[Child Process] Result dictionary: {dict(result_dict)}")
|
103
89
|
|
104
90
|
except Exception as e:
|
105
|
-
#
|
91
|
+
# In case of error, store information about the error
|
106
92
|
result_dict["status"] = "error"
|
107
93
|
result_dict["error"] = str(e)
|
108
94
|
result_dict["traceback"] = traceback.format_exc()
|
109
95
|
|
110
|
-
#
|
111
|
-
|
96
|
+
# For debug
|
97
|
+
error_print(f"[Child Process] Error occurred: {str(e)}")
|
112
98
|
|
113
99
|
def run(self, function: Callable[..., T], *args, **kwargs) -> "ParallelRunner[T]":
|
114
100
|
"""
|
@@ -121,43 +107,38 @@ class ParallelRunner(Generic[T]):
|
|
121
107
|
|
122
108
|
Returns:
|
123
109
|
self: Returns the instance itself to allow chained calls
|
110
|
+
"""
|
111
|
+
try:
|
112
|
+
# Clear previous result, if any
|
113
|
+
if self._result_dict:
|
114
|
+
self._result_dict.clear()
|
115
|
+
|
116
|
+
# Configure initial values in the shared dictionary
|
117
|
+
self._result_dict["status"] = "running"
|
118
|
+
|
119
|
+
# Start the process with the static helper function
|
120
|
+
if self.display_message:
|
121
|
+
self._process = Process(
|
122
|
+
target=ParallelRunner._execute_function_w_disp_msg,
|
123
|
+
args=(function, args, kwargs, self._result_dict),
|
124
|
+
)
|
125
|
+
else:
|
126
|
+
self._process = Process(
|
127
|
+
target=ParallelRunner._execute_function,
|
128
|
+
args=(function, args, kwargs, self._result_dict),
|
129
|
+
)
|
124
130
|
|
125
|
-
|
126
|
-
|
127
|
-
|
131
|
+
self._process.daemon = True # Child process terminates when main terminates
|
132
|
+
self._process.start()
|
133
|
+
self._start_time = time.time()
|
128
134
|
|
129
|
-
|
130
|
-
|
131
|
-
*args: Argumentos posicionais para a função
|
132
|
-
**kwargs: Argumentos nomeados para a função
|
135
|
+
if self.display_message:
|
136
|
+
success_print(f"Parallel process started successfully")
|
133
137
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
if self._result_dict:
|
139
|
-
self._result_dict.clear()
|
140
|
-
|
141
|
-
# Configura valores iniciais no dicionário compartilhado
|
142
|
-
self._result_dict["status"] = "running"
|
143
|
-
|
144
|
-
# Inicia o processo com a função auxiliar estática
|
145
|
-
if self.display_message:
|
146
|
-
self._process = Process(
|
147
|
-
target=ParallelRunner._execute_function_w_disp_msg,
|
148
|
-
args=(function, args, kwargs, self._result_dict),
|
149
|
-
)
|
150
|
-
else:
|
151
|
-
self._process = Process(
|
152
|
-
target=ParallelRunner._execute_function,
|
153
|
-
args=(function, args, kwargs, self._result_dict),
|
154
|
-
)
|
155
|
-
|
156
|
-
self._process.daemon = True # Processo filho termina quando o principal termina
|
157
|
-
self._process.start()
|
158
|
-
self._start_time = time.time()
|
159
|
-
|
160
|
-
return self
|
138
|
+
return self
|
139
|
+
|
140
|
+
except Exception as e:
|
141
|
+
raise ParallelRunnerError(f"Error starting parallel process: {str(e)}") from e
|
161
142
|
|
162
143
|
def is_running(self) -> bool:
|
163
144
|
"""
|
@@ -165,17 +146,15 @@ class ParallelRunner(Generic[T]):
|
|
165
146
|
|
166
147
|
Returns:
|
167
148
|
bool: True if the process is still running, False otherwise
|
168
|
-
|
169
|
-
pt-br
|
170
|
-
------
|
171
|
-
Verifica se o processo ainda está em execução.
|
172
|
-
|
173
|
-
Retorna:
|
174
|
-
bool: True se o processo ainda estiver em execução, False caso contrário
|
175
149
|
"""
|
176
|
-
|
150
|
+
try:
|
151
|
+
if self._process is None:
|
152
|
+
return False
|
153
|
+
return self._process.is_alive()
|
154
|
+
except Exception as e:
|
155
|
+
if self.display_message:
|
156
|
+
error_print(f"Error checking process status: {str(e)}")
|
177
157
|
return False
|
178
|
-
return self._process.is_alive()
|
179
158
|
|
180
159
|
def get_result(self, timeout: Optional[float] = 60, terminate_on_timeout: bool = True) -> Dict[str, Any]:
|
181
160
|
"""
|
@@ -187,123 +166,147 @@ class ParallelRunner(Generic[T]):
|
|
187
166
|
terminate_on_timeout: If True, terminates the process if the timeout is reached.
|
188
167
|
|
189
168
|
Returns:
|
190
|
-
|
169
|
+
Dict containing:
|
191
170
|
- success: bool indicating if the operation was successful.
|
192
171
|
- result: result of the function (if successful).
|
193
172
|
- error: error message (if any).
|
194
173
|
- traceback: full stack trace (if an error occurred).
|
195
174
|
- execution_time: execution time in seconds.
|
196
175
|
- terminated: True if the process was terminated due to timeout.
|
197
|
-
|
198
|
-
pt-br
|
199
|
-
------
|
200
|
-
|
201
|
-
Recupera o resultado da execução paralela.
|
202
|
-
|
203
|
-
Args:
|
204
|
-
timeout: Tempo máximo (em segundos) para aguardar o término do processo.
|
205
|
-
None significa aguardar indefinidamente.
|
206
|
-
terminate_on_timeout: Se True, termina o processo se o tempo limite for atingido.
|
207
|
-
|
208
|
-
Retorna:
|
209
|
-
- Dicionário contendo:
|
210
|
-
- success: bool indicando se a operação foi bem-sucedida.
|
211
|
-
- result: resultado da função (se bem-sucedida).
|
212
|
-
- error: mensagem de erro (se houver).
|
213
|
-
- traceback: rastreamento completo da pilha (se ocorreu um erro).
|
214
|
-
- execution_time: tempo de execução em segundos.
|
215
|
-
- terminated: True se o processo foi terminado devido ao tempo limite.
|
216
176
|
"""
|
177
|
+
try:
|
178
|
+
if self._process is None:
|
179
|
+
return {
|
180
|
+
"success": False,
|
181
|
+
"error": "No process was started",
|
182
|
+
"execution_time": 0,
|
183
|
+
"terminated": False,
|
184
|
+
}
|
185
|
+
|
186
|
+
# Wait for the process to finish with timeout
|
187
|
+
self._process.join(timeout=timeout)
|
188
|
+
execution_time = time.time() - self._start_time
|
189
|
+
|
190
|
+
# Prepare the response dictionary
|
191
|
+
result = {"execution_time": execution_time, "terminated": False}
|
192
|
+
|
193
|
+
# Debug - show the shared dictionary
|
194
|
+
if self.display_message:
|
195
|
+
success_print(f"[Main Process] Shared dictionary: {dict(self._result_dict)}")
|
196
|
+
|
197
|
+
# Check if the process finished or reached timeout
|
198
|
+
if self._process.is_alive():
|
199
|
+
if terminate_on_timeout:
|
200
|
+
try:
|
201
|
+
self._process.terminate()
|
202
|
+
self._process.join(timeout=1) # Small timeout to ensure process terminates
|
203
|
+
result["terminated"] = True
|
204
|
+
result["success"] = False
|
205
|
+
result["error"] = f"Operation cancelled due to timeout after {execution_time:.2f} seconds"
|
206
|
+
|
207
|
+
if self.display_message:
|
208
|
+
alert_print(f"Process terminated due to timeout")
|
209
|
+
|
210
|
+
except Exception as e:
|
211
|
+
result["success"] = False
|
212
|
+
result["error"] = f"Error terminating process: {str(e)}"
|
213
|
+
if self.display_message:
|
214
|
+
error_print(f"Error terminating process: {str(e)}")
|
215
|
+
else:
|
216
|
+
result["success"] = False
|
217
|
+
result["error"] = f"Operation still running after {execution_time:.2f} seconds"
|
218
|
+
else:
|
219
|
+
# Process finished normally - check the status
|
220
|
+
try:
|
221
|
+
status = self._result_dict.get("status", "unknown")
|
222
|
+
|
223
|
+
if status == "success":
|
224
|
+
result["success"] = True
|
225
|
+
# Ensure the result is being copied correctly
|
226
|
+
if "result" in self._result_dict:
|
227
|
+
result["result"] = self._result_dict["result"]
|
228
|
+
if self.display_message:
|
229
|
+
success_print("Result retrieved successfully")
|
230
|
+
else:
|
231
|
+
result["success"] = False
|
232
|
+
result["error"] = "Result not found in shared dictionary"
|
233
|
+
if self.display_message:
|
234
|
+
error_print("Result not found in shared dictionary")
|
235
|
+
else:
|
236
|
+
result["success"] = False
|
237
|
+
result["error"] = self._result_dict.get("error", "Unknown error")
|
238
|
+
if "traceback" in self._result_dict:
|
239
|
+
result["traceback"] = self._result_dict["traceback"]
|
240
|
+
if self.display_message:
|
241
|
+
error_print(f"Process failed with error: {result['error']}")
|
242
|
+
|
243
|
+
except Exception as e:
|
244
|
+
result["success"] = False
|
245
|
+
result["error"] = f"Error retrieving result from shared dictionary: {str(e)}"
|
246
|
+
if self.display_message:
|
247
|
+
error_print(f"Error retrieving result: {str(e)}")
|
248
|
+
|
249
|
+
# Finalize the Manager if the process finished and we're no longer waiting for result
|
250
|
+
if not self._process.is_alive() and (result.get("success", False) or result.get("terminated", False)):
|
251
|
+
self._cleanup()
|
217
252
|
|
218
|
-
|
253
|
+
return result
|
254
|
+
|
255
|
+
except Exception as e:
|
256
|
+
error_message = f"Error getting result from parallel process: {str(e)}"
|
257
|
+
if self.display_message:
|
258
|
+
error_print(error_message)
|
219
259
|
return {
|
220
260
|
"success": False,
|
221
|
-
"error":
|
261
|
+
"error": error_message,
|
222
262
|
"execution_time": 0,
|
223
263
|
"terminated": False,
|
224
264
|
}
|
225
265
|
|
226
|
-
# Aguarda o processo terminar com tempo limite
|
227
|
-
self._process.join(timeout=timeout)
|
228
|
-
execution_time = time.time() - self._start_time
|
229
|
-
|
230
|
-
# Preparamos o dicionário de resposta
|
231
|
-
result = {"execution_time": execution_time, "terminated": False}
|
232
|
-
|
233
|
-
# Debug - mostra o dicionário compartilhado
|
234
|
-
if self.display_message:
|
235
|
-
print(f"[Processo Principal] Dicionário compartilhado: {dict(self._result_dict)}")
|
236
|
-
|
237
|
-
# Verifica se o processo terminou ou se atingiu o timeout
|
238
|
-
if self._process.is_alive():
|
239
|
-
if terminate_on_timeout:
|
240
|
-
self._process.terminate()
|
241
|
-
self._process.join(timeout=1) # Pequeno timeout para garantir que o processo termine
|
242
|
-
result["terminated"] = True
|
243
|
-
result["success"] = False
|
244
|
-
result["error"] = f"Operação cancelada por timeout após {execution_time:.2f} segundos"
|
245
|
-
else:
|
246
|
-
result["success"] = False
|
247
|
-
result["error"] = f"Operação ainda em execução após {execution_time:.2f} segundos"
|
248
|
-
else:
|
249
|
-
# Processo terminou normalmente - verificamos o status
|
250
|
-
status = self._result_dict.get("status", "unknown")
|
251
|
-
|
252
|
-
if status == "success":
|
253
|
-
result["success"] = True
|
254
|
-
# Garantimos que o resultado está sendo copiado corretamente
|
255
|
-
if "result" in self._result_dict:
|
256
|
-
result["result"] = self._result_dict["result"]
|
257
|
-
else:
|
258
|
-
result["success"] = False
|
259
|
-
result["error"] = "Resultado não encontrado no dicionário compartilhado"
|
260
|
-
else:
|
261
|
-
result["success"] = False
|
262
|
-
result["error"] = self._result_dict.get("error", "Erro desconhecido")
|
263
|
-
if "traceback" in self._result_dict:
|
264
|
-
result["traceback"] = self._result_dict["traceback"]
|
265
|
-
|
266
|
-
# Finaliza o Manager se o processo terminou e não estamos mais esperando resultado
|
267
|
-
if not self._process.is_alive() and (result.get("success", False) or result.get("terminated", False)):
|
268
|
-
self._cleanup()
|
269
|
-
|
270
|
-
return result
|
271
|
-
|
272
266
|
def terminate(self) -> None:
|
273
267
|
"""
|
274
268
|
Terminates the running process.
|
275
|
-
|
276
|
-
pt-br
|
277
|
-
------
|
278
|
-
Termina o processo em execução.
|
279
269
|
"""
|
280
|
-
|
281
|
-
self._process.
|
282
|
-
|
283
|
-
|
270
|
+
try:
|
271
|
+
if self._process and self._process.is_alive():
|
272
|
+
self._process.terminate()
|
273
|
+
self._process.join(timeout=1)
|
274
|
+
self._cleanup()
|
275
|
+
|
276
|
+
if self.display_message:
|
277
|
+
success_print("Process terminated successfully")
|
278
|
+
|
279
|
+
except Exception as e:
|
280
|
+
if self.display_message:
|
281
|
+
error_print(f"Error terminating process: {str(e)}")
|
284
282
|
|
285
283
|
def _cleanup(self) -> None:
|
286
284
|
"""
|
287
285
|
Cleans up resources used by the process.
|
288
|
-
|
289
|
-
pt-br
|
290
|
-
------
|
291
|
-
Limpa os recursos utilizados pelo processo.
|
292
286
|
"""
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
287
|
+
try:
|
288
|
+
if hasattr(self, "_manager") and self._manager is not None:
|
289
|
+
try:
|
290
|
+
self._manager.shutdown()
|
291
|
+
except Exception as e:
|
292
|
+
if self.display_message:
|
293
|
+
error_print(f"Error shutting down manager: {str(e)}")
|
294
|
+
self._manager = None
|
295
|
+
self._process = None
|
296
|
+
|
297
|
+
if self.display_message:
|
298
|
+
success_print("Resources cleaned up successfully")
|
299
|
+
|
300
|
+
except Exception as e:
|
301
|
+
if self.display_message:
|
302
|
+
error_print(f"Error during cleanup: {str(e)}")
|
300
303
|
|
301
304
|
def __del__(self):
|
302
305
|
"""
|
303
306
|
Destructor of the class, ensures resources are released.
|
304
|
-
|
305
|
-
pt-br
|
306
|
-
------
|
307
|
-
Destrutor da classe, garante que recursos sejam liberados.
|
308
307
|
"""
|
309
|
-
|
308
|
+
try:
|
309
|
+
self.terminate()
|
310
|
+
except Exception:
|
311
|
+
# Silently handle any errors during destruction
|
312
|
+
pass
|