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.
@@ -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
- self._manager = Manager()
44
- self._result_dict = self._manager.dict()
45
- self._process = None
46
- self._start_time = None
47
- self.display_message = display_message
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
- # Executa a função do usuário com os argumentos fornecidos
56
+ # Execute the user function with the provided arguments
62
57
  result = function(*args, **kwargs)
63
58
 
64
- # Armazena o resultado no dicionário compartilhado
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
- # Em caso de erro, armazena informações sobre o erro
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
- # Para debug
79
- print(f"[Processo Filho] Erro ocorrido: {str(e)}")
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
- # Executa a função do usuário com os argumentos fornecidos
79
+ # Execute the user function with the provided arguments
94
80
  result = function(*args, **kwargs)
95
81
 
96
- # Armazena o resultado no dicionário compartilhado
82
+ # Store the result in the shared dictionary
97
83
  result_dict["status"] = "success"
98
84
  result_dict["result"] = result
99
85
 
100
- # Para debug
101
- print(f"[Processo Filho] Resultado calculado: {result}")
102
- print(f"[Processo Filho] Dicionário de resultados: {dict(result_dict)}")
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
- # Em caso de erro, armazena informações sobre o erro
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
- # Para debug
111
- print(f"[Processo Filho] Erro ocorrido: {str(e)}")
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
- pt-br
126
- ------
127
- Inicia a execução da função em um processo paralelo.
131
+ self._process.daemon = True # Child process terminates when main terminates
132
+ self._process.start()
133
+ self._start_time = time.time()
128
134
 
129
- Args:
130
- function: Função a ser executada em paralelo
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
- Returns:
135
- self: Retorna a própria instância para permitir chamadas encadeadas
136
- """
137
- # Limpar resultado anterior, se houver
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
- if self._process is None:
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
- - Dict containing:
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
- if self._process is None:
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": "Nenhum processo foi iniciado",
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
- if self._process and self._process.is_alive():
281
- self._process.terminate()
282
- self._process.join(timeout=1)
283
- self._cleanup()
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
- if hasattr(self, "_manager") and self._manager is not None:
294
- try:
295
- self._manager.shutdown()
296
- except Exception:
297
- pass
298
- self._manager = None
299
- self._process = None
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
- self.terminate()
308
+ try:
309
+ self.terminate()
310
+ except Exception:
311
+ # Silently handle any errors during destruction
312
+ pass