rpa-suite 1.6.2__tar.gz → 1.6.4__tar.gz

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.
Files changed (38) hide show
  1. {rpa_suite-1.6.2/rpa_suite.egg-info → rpa_suite-1.6.4}/PKG-INFO +5 -17
  2. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/README.md +3 -15
  3. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/__init__.py +1 -1
  4. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/__init__.py +5 -1
  5. rpa_suite-1.6.4/rpa_suite/core/artemis.py +445 -0
  6. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/asyncrun.py +15 -8
  7. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/browser.py +44 -41
  8. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/clock.py +46 -13
  9. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/date.py +41 -16
  10. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/dir.py +29 -72
  11. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/email.py +26 -15
  12. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/file.py +46 -43
  13. rpa_suite-1.6.4/rpa_suite/core/iris.py +216 -0
  14. rpa_suite-1.6.4/rpa_suite/core/log.py +266 -0
  15. rpa_suite-1.6.4/rpa_suite/core/parallel.py +312 -0
  16. rpa_suite-1.6.4/rpa_suite/core/print.py +220 -0
  17. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/regex.py +26 -26
  18. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/core/validate.py +20 -76
  19. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/functions/__init__.py +1 -1
  20. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/suite.py +13 -1
  21. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/utils/__init__.py +1 -1
  22. rpa_suite-1.6.4/rpa_suite/utils/system.py +160 -0
  23. {rpa_suite-1.6.2 → rpa_suite-1.6.4/rpa_suite.egg-info}/PKG-INFO +5 -17
  24. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite.egg-info/SOURCES.txt +1 -0
  25. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite.egg-info/requires.txt +1 -1
  26. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/setup.py +1 -2
  27. rpa_suite-1.6.2/rpa_suite/core/iris.py +0 -208
  28. rpa_suite-1.6.2/rpa_suite/core/log.py +0 -178
  29. rpa_suite-1.6.2/rpa_suite/core/parallel.py +0 -309
  30. rpa_suite-1.6.2/rpa_suite/core/print.py +0 -197
  31. rpa_suite-1.6.2/rpa_suite/utils/system.py +0 -157
  32. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/LICENSE +0 -0
  33. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/pyproject.toml +0 -0
  34. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/functions/__create_ss_dir.py +0 -0
  35. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite/functions/_printer.py +0 -0
  36. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite.egg-info/dependency_links.txt +0 -0
  37. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/rpa_suite.egg-info/top_level.txt +0 -0
  38. {rpa_suite-1.6.2 → rpa_suite-1.6.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rpa_suite
3
- Version: 1.6.2
3
+ Version: 1.6.4
4
4
  Summary: Conjunto de ferramentas essenciais para Automação RPA com Python, que facilitam o dia a dia de desenvolvimento.
5
5
  Author: Camilo Costa de Carvalho
6
6
  Author-email: camilo.carvalho@vettracode.com
@@ -32,7 +32,7 @@ Requires-Dist: loguru
32
32
  Requires-Dist: typing
33
33
  Requires-Dist: pillow
34
34
  Requires-Dist: pyautogui
35
- Requires-Dist: opencv-pythonrequests
35
+ Requires-Dist: requests
36
36
  Dynamic: author
37
37
  Dynamic: author-email
38
38
  Dynamic: classifier
@@ -292,33 +292,21 @@ O módulo principal do rpa-suite é dividido em categorias. Cada categoria cont
292
292
 
293
293
  ## Release Notes
294
294
 
295
- ### Versão: **Beta 1.6.2**
295
+ ### Versão: **Beta 1.6.4**
296
296
 
297
297
  - **Data de Lançamento:** *20/02/2024*
298
- - **Última Atualização:** 08/06/2025
298
+ - **Última Atualização:** 16/09/2025
299
299
  - **Status:** Em desenvolvimento
300
300
 
301
301
  Esta versão marca um grande avanço no desenvolvimento da RPA Suite, trazendo melhorias significativas na arquitetura, novas funcionalidades e maior simplicidade no uso. Confira as principais mudanças abaixo.
302
302
 
303
303
  ### Notas:
304
- - atualização 1.6.0
304
+ - atualização 1.6.4
305
305
  - Adição Módulo: Iris (OCR-IA)
306
306
  - Feat.: leitura de documento (aceita multiplos formatos)
307
307
  - Feat.: leitura em lote (multiplos docmumentos em uma unica chamada)
308
308
  - Melhoria de docstrings
309
309
 
310
- - atualização 1.5.9
311
- - Atualização de Linters e Formatters
312
- - black
313
- - pylint
314
- - bandit
315
- - flake8
316
- - isort
317
- - pyupgrade
318
- - detect-secrets
319
- - autoflake
320
-
321
-
322
310
  ## Mais Sobre
323
311
 
324
312
  Para mais informações, visite os links abaixo:
@@ -245,33 +245,21 @@ O módulo principal do rpa-suite é dividido em categorias. Cada categoria cont
245
245
 
246
246
  ## Release Notes
247
247
 
248
- ### Versão: **Beta 1.6.2**
248
+ ### Versão: **Beta 1.6.4**
249
249
 
250
250
  - **Data de Lançamento:** *20/02/2024*
251
- - **Última Atualização:** 08/06/2025
251
+ - **Última Atualização:** 16/09/2025
252
252
  - **Status:** Em desenvolvimento
253
253
 
254
254
  Esta versão marca um grande avanço no desenvolvimento da RPA Suite, trazendo melhorias significativas na arquitetura, novas funcionalidades e maior simplicidade no uso. Confira as principais mudanças abaixo.
255
255
 
256
256
  ### Notas:
257
- - atualização 1.6.0
257
+ - atualização 1.6.4
258
258
  - Adição Módulo: Iris (OCR-IA)
259
259
  - Feat.: leitura de documento (aceita multiplos formatos)
260
260
  - Feat.: leitura em lote (multiplos docmumentos em uma unica chamada)
261
261
  - Melhoria de docstrings
262
262
 
263
- - atualização 1.5.9
264
- - Atualização de Linters e Formatters
265
- - black
266
- - pylint
267
- - bandit
268
- - flake8
269
- - isort
270
- - pyupgrade
271
- - detect-secrets
272
- - autoflake
273
-
274
-
275
263
  ## Mais Sobre
276
264
 
277
265
  Para mais informações, visite os links abaixo:
@@ -66,7 +66,7 @@ Módulos disponíveis:
66
66
  ``Iris``: Objeto Iris Automação de funções para converter documentos com OCR + IA baseado em ``docling``
67
67
  """
68
68
 
69
- __version__ = "1.6.1"
69
+ __version__ = "1.6.4"
70
70
 
71
71
  # allows importing the rpa_suite module without the package name
72
72
  from .suite import rpa
@@ -48,4 +48,8 @@ if importlib.util.find_spec("selenium") and importlib.util.find_spec("webdriver_
48
48
  if importlib.util.find_spec("docling"):
49
49
  from .iris import Iris
50
50
 
51
- __version__ = "1.6.1"
51
+ # from .iris import Artemis
52
+ if importlib.util.find_spec("pyautogui"):
53
+ from .artemis import Artemis
54
+
55
+ __version__ = "1.6.4"
@@ -0,0 +1,445 @@
1
+ # rpa_suite/core/artemis.py
2
+
3
+ # imports standard
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Tuple, Union, Optional
7
+ from time import time
8
+ from time import sleep
9
+
10
+ # imports external
11
+ import pyautogui as artemis_engine
12
+
13
+ # imports internos
14
+ # imports internal
15
+ from rpa_suite.functions._printer import alert_print, success_print
16
+
17
+ # constantes
18
+ OPENCV_AVAILABLE_FROM_ARTEMIS = False
19
+
20
+
21
+ class ArtemisError(Exception):
22
+ """Custom exception for Artemis errors."""
23
+ def __init__(self, message):
24
+ super().__init__(f'ArtemisError: {message}')
25
+
26
+
27
+ class Artemis:
28
+ """
29
+ Artemis:
30
+ ----------
31
+ Intelligent desktop automation based on computer vision.
32
+
33
+ Specialized in locating and interacting with visual elements
34
+ in the graphical interface, optimized for robust RPA automation.
35
+ """
36
+
37
+ def __init__(self):
38
+ """
39
+ Artemis:
40
+ ----------
41
+ Intelligent desktop automation based on computer vision.
42
+
43
+ Specialized in locating and interacting with visual elements
44
+ in the graphical interface, optimized for robust RPA automation.
45
+ """
46
+ artemis_engine.FAILSAFE = True # Move mouse to top-left corner to stop
47
+ artemis_engine.PAUSE = 0.1 # Default pause between commands
48
+ global OPENCV_AVAILABLE_FROM_ARTEMIS
49
+ OPENCV_AVAILABLE_FROM_ARTEMIS = self.__check_opencv_availability()
50
+
51
+ # pylint: disable=import-outside-toplevel
52
+ def __check_opencv_availability() -> bool:
53
+ """Checks if OpenCV is available in the system."""
54
+ try:
55
+ # pylint: disable=import-outside-toplevel
56
+ import cv2 # pylint: disable=unused-import
57
+
58
+ return True
59
+ except ImportError:
60
+ return False
61
+
62
+ def click_image(
63
+ self,
64
+ image_label: str,
65
+ images_folder: Union[str, Path] = "resource",
66
+ confidence: float = 0.78,
67
+ timeout: float = 10.0,
68
+ click_center: bool = True,
69
+ click_button: str = "left",
70
+ double_click: bool = False,
71
+ search_interval: float = 0.5,
72
+ region: Optional[Tuple[int, int, int, int]] = None,
73
+ grayscale: bool = True,
74
+ display_details: bool = False,
75
+ verbose: bool = False
76
+ ) -> Union[Tuple[int, int], bool]:
77
+ """
78
+ Locates an image on the screen and clicks on it.
79
+
80
+ This function searches for a specific image on the screen using PyAutoGUI
81
+ and performs a click at the found position. Implements search with timeout
82
+ and different confidence levels for better accuracy (when OpenCV is available).
83
+
84
+ Args:
85
+ image_label (str): Image file name (with or without extension).
86
+ Ex: 'ok_button' or 'ok_button.png'
87
+ images_folder (Union[str, Path], optional): Path to images folder.
88
+ Default: "resource"
89
+ confidence (float, optional): Confidence level for location (0.0-1.0).
90
+ Requires OpenCV installed. If not available, will be ignored.
91
+ High values = higher precision, lower tolerance.
92
+ Default: 0.78
93
+ timeout (float, optional): Time limit in seconds for search.
94
+ Default: 10.0
95
+ click_center (bool, optional): If True, clicks at the center of the image.
96
+ If False, clicks at the top-left corner.
97
+ Default: True
98
+ click_button (str, optional): Mouse button ('left', 'right', 'middle').
99
+ Default: 'left'
100
+ double_click (bool, optional): If True, performs double click.
101
+ Default: False
102
+ search_interval (float, optional): Interval between search attempts.
103
+ Default: 0.5 seconds
104
+ region (Optional[Tuple[int, int, int, int]], optional): Screen region to search.
105
+ Format: (x, y, width, height)
106
+ Default: None (entire screen)
107
+ grayscale (bool, optional): If True, searches in grayscale (faster).
108
+ Default: True
109
+ display_details (bool, optional): If True, displays details.
110
+ Default: False
111
+ verbose (bool, optional): If True, displays verbose output.
112
+ Default: False
113
+
114
+ Returns:
115
+ Union[Tuple[int, int], bool]: Coordinates (x, y) of the image center if found
116
+ or False if not found within timeout.
117
+
118
+ Raises:
119
+ ImageClickError: If there's an error in configuration or execution.
120
+ FileNotFoundError: If the image file is not found.
121
+ ValueError: If parameters are invalid.
122
+
123
+ Note:
124
+ To use the confidence parameter, install OpenCV: pip install opencv-python
125
+ Without OpenCV, the function will work with exact pixel matching.
126
+
127
+ Example:
128
+ >>> # Search and click on a button
129
+ >>> position = click_image('save_button.png', confidence=0.9, timeout=5.0)
130
+ >>> if position:
131
+ ... print(f"Clicked at position: {position}")
132
+ ... else:
133
+ ... print("Image not found")
134
+
135
+ >>> # Search in specific screen region
136
+ >>> region_result = click_image(
137
+ ... 'menu_icon',
138
+ ... region=(0, 0, 500, 300), # Search only in top-left corner
139
+ ... confidence=0.7
140
+ ... )
141
+ """
142
+
143
+ # Parameter validation
144
+ self._validate_parameters(confidence, timeout, search_interval, click_button, region)
145
+
146
+ # Resolve full image path
147
+ image_path = self._resolve_image_path(image_label, images_folder)
148
+
149
+ # Warning if confidence will be ignored
150
+ if confidence != 0.8 and not OPENCV_AVAILABLE_FROM_ARTEMIS:
151
+ if verbose: alert_print(f"Parameter confidence={confidence} will be ignored. " + "Install OpenCV: pip install opencv-python")
152
+
153
+ if verbose: print(f"Starting image search: {image_path}")
154
+ if display_details:
155
+ if verbose: print(
156
+ f"Settings: confidence={'N/A' if not OPENCV_AVAILABLE_FROM_ARTEMIS else confidence}, "
157
+ + f"timeout={timeout}s, region={region}"
158
+ )
159
+
160
+ # Temporary PyAutoGUI settings
161
+ original_pause = artemis_engine.PAUSE
162
+ artemis_engine.PAUSE = 0.05 # Reduce pause for faster search
163
+
164
+ try:
165
+ # Execute search with timeout
166
+ position = self._search_image_with_timeout(
167
+ image_path=image_path,
168
+ confidence=confidence,
169
+ timeout=timeout,
170
+ search_interval=search_interval,
171
+ region=region,
172
+ grayscale=grayscale,
173
+ )
174
+
175
+ if not position:
176
+ if verbose: alert_print(f"Image not found after {timeout}s: {image_path.name}")
177
+ return False
178
+
179
+ # Calculate click position
180
+ click_position = self._calculate_click_position(position, click_center)
181
+
182
+ # Perform click
183
+ self._perform_click(click_position, click_button, double_click)
184
+
185
+ # print(f"Click performed!")
186
+ return click_position
187
+
188
+ except Exception as e:
189
+ error_msg = f"Error processing image click {image_path.name}: {str(e)}"
190
+ raise ArtemisError(error_msg) from e
191
+
192
+ finally:
193
+ # Restore original settings
194
+ artemis_engine.PAUSE = original_pause
195
+
196
+
197
+ def find_image_position(
198
+ self,
199
+ image_label: str,
200
+ images_folder: Union[str, Path] = "resource",
201
+ confidence: float = 0.8,
202
+ timeout: float = 5.0,
203
+ region: Optional[Tuple[int, int, int, int]] = None,
204
+ grayscale: bool = False,
205
+ verbose: bool = False,
206
+ ) -> Union[Tuple[int, int], bool]:
207
+ """
208
+ Finds the position of an image on the screen without clicking.
209
+
210
+ Utility function to only locate an image without performing a click.
211
+ Useful for checking element presence or getting coordinates.
212
+
213
+ Args:
214
+ image_label (str): Image file name.
215
+ images_folder (Union[str, Path], optional): Images folder. Default: "images"
216
+ confidence (float, optional): Confidence level. Default: 0.8
217
+ timeout (float, optional): Timeout in seconds. Default: 5.0
218
+ region (Optional[Tuple], optional): Search region. Default: None
219
+ grayscale (bool, optional): Search in grayscale. Default: False
220
+
221
+ Returns:
222
+ Union[Tuple[int, int], bool]: Image center coordinates or False.
223
+ """
224
+
225
+ self._validate_parameters(confidence, timeout, 0.5, "left", region)
226
+ image_path = self._resolve_image_path(image_label, images_folder)
227
+
228
+ try:
229
+ position = self._search_image_with_timeout(
230
+ image_path=image_path,
231
+ confidence=confidence,
232
+ timeout=timeout,
233
+ search_interval=0.5,
234
+ region=region,
235
+ grayscale=grayscale,
236
+ )
237
+
238
+ if position:
239
+ return self._calculate_click_position(position, click_center=True)
240
+ return False
241
+
242
+ except Exception as e:
243
+ error_msg = f"Error searching for image {image_path.name}: {str(e)}"
244
+ raise ArtemisError(error_msg) from e
245
+
246
+ def _validate_parameters(
247
+ self,
248
+ confidence: float,
249
+ timeout: float,
250
+ search_interval: float,
251
+ click_button: str,
252
+ region: Optional[Tuple[int, int, int, int]],
253
+ ) -> None:
254
+ """Validates function input parameters."""
255
+
256
+ if not 0.0 <= confidence <= 1.0:
257
+ raise ValueError(f"Confidence must be between 0.0 and 1.0, received: {confidence}")
258
+
259
+ if timeout <= 0:
260
+ raise ValueError(f"Timeout must be positive, received: {timeout}")
261
+
262
+ if search_interval <= 0:
263
+ raise ValueError(f"Search interval must be positive, received: {search_interval}")
264
+
265
+ if click_button not in ["left", "right", "middle"]:
266
+ raise ValueError(f"Click button must be 'left', 'right' or 'middle', received: {click_button}")
267
+
268
+ if region is not None:
269
+ if not isinstance(region, (tuple, list)) or len(region) != 4:
270
+ raise ValueError("Region must be a tuple with 4 elements: (x, y, width, height)")
271
+
272
+ if any(not isinstance(val, int) or val < 0 for val in region):
273
+ raise ValueError("All region values must be non-negative integers")
274
+
275
+
276
+ def _resolve_image_path(image_label: str, images_folder: Union[str, Path]) -> Path:
277
+ """Resolves the full path to the image file."""
278
+
279
+ folder_path = Path(images_folder)
280
+
281
+ # If image_label already has extension, use directly
282
+ if "." in image_label:
283
+ image_path = folder_path / image_label
284
+ else:
285
+ # Try different common extensions
286
+ extensions = [".png", ".jpg", ".jpeg", ".bmp", ".gif"]
287
+ image_path = None
288
+
289
+ for ext in extensions:
290
+ candidate = folder_path / f"{image_label}{ext}"
291
+ if candidate.exists():
292
+ image_path = candidate
293
+ break
294
+
295
+ if not image_path:
296
+ # If not found, use .png as default for clearer error
297
+ image_path = folder_path / f"{image_label}.png"
298
+
299
+ if not image_path.exists():
300
+ raise FileNotFoundError(f"Image file not found: {image_path}")
301
+
302
+ return image_path
303
+
304
+
305
+ def _search_image_with_timeout(
306
+ self,
307
+ image_path: Path,
308
+ confidence: float,
309
+ timeout: float,
310
+ search_interval: float,
311
+ region: Optional[Tuple[int, int, int, int]],
312
+ grayscale: bool,
313
+ verbose: bool = False,
314
+ ) -> Optional[any]:
315
+ """Searches for image on screen with timeout, considering OpenCV availability."""
316
+
317
+ start_time = time()
318
+ attempts = 0
319
+
320
+ while time() - start_time < timeout:
321
+ attempts += 1
322
+
323
+ try:
324
+ # Build arguments for locateOnScreen based on OpenCV availability
325
+ locate_args = {"image": str(image_path), "region": region, "grayscale": grayscale}
326
+
327
+ # Add confidence only if OpenCV is available
328
+ if OPENCV_AVAILABLE_FROM_ARTEMIS:
329
+ locate_args["confidence"] = confidence
330
+
331
+ # Search for image on screen
332
+ location = artemis_engine.locateOnScreen(**locate_args)
333
+
334
+ if location:
335
+ if verbose: print(f"Image found on attempt {attempts}.")
336
+ return location
337
+
338
+ except artemis_engine.ImageNotFoundException:
339
+ # Image not found in this attempt
340
+ pass
341
+ except TypeError as e:
342
+ if "confidence" in str(e):
343
+ # Fallback if confidence error still occurs
344
+ alert_print("Confidence error detected, trying without parameter...")
345
+ try:
346
+ location = artemis_engine.locateOnScreen(str(image_path), region=region, grayscale=grayscale)
347
+ if location:
348
+ if verbose: print(f"Image found on attempt {attempts} (without confidence): {location}")
349
+ return location
350
+ except Exception as error:
351
+ ArtemisError(f"Failed attempt without confidence: {error}.") from e
352
+ else:
353
+ ArtemisError(f"Error during image search (attempt {attempts}): {e}") from e
354
+ except Exception as e:
355
+ ArtemisError(f"Error during image search (attempt {attempts}): {e}") from e
356
+
357
+ # Wait before next attempt
358
+ if time() - start_time < timeout:
359
+ sleep(search_interval)
360
+
361
+ if verbose:
362
+ success_print(f"Search completed after {attempts} attempts in {timeout}s")
363
+ return None
364
+
365
+
366
+ def _calculate_click_position(image_box: any, click_center: bool) -> Tuple[int, int]:
367
+ """Calculates the exact click position based on image location."""
368
+
369
+ if click_center:
370
+ # Click at image center
371
+ center_x = image_box.left + image_box.width // 2
372
+ center_y = image_box.top + image_box.height // 2
373
+ return (center_x, center_y)
374
+ # Click at top-left corner
375
+ return (image_box.left, image_box.top)
376
+
377
+
378
+ def _perform_click(position: Tuple[int, int], click_button: str, double_click: bool, verbose: bool = False) -> None:
379
+ """Performs click at specified position."""
380
+
381
+ try:
382
+ x, y = position
383
+
384
+ # Move mouse to position (optional, but helps with visualization)
385
+ artemis_engine.moveTo(x, y, duration=0.1)
386
+
387
+ # Perform click
388
+ if double_click:
389
+ artemis_engine.doubleClick(x, y, button=click_button)
390
+ if verbose:
391
+ success_print(f"Double click performed at ({x}, {y}) with {click_button} button.")
392
+ else:
393
+ artemis_engine.click(x, y, button=click_button)
394
+ if verbose:
395
+ success_print(f"Click performed at ({x}, {y}) with {click_button} button.")
396
+ except Exception as e:
397
+ ArtemisError(f"Error performing click: {str(e)}.") from e
398
+
399
+
400
+ # Convenience functions for specific cases
401
+ def wait_and_click(
402
+ self,
403
+ image_label: str,
404
+ images_folder: Union[str, Path] = "resource",
405
+ confidence: float = 0.8,
406
+ timeout: float = 30.0
407
+ ) -> Union[Tuple[int, int], bool]:
408
+
409
+ """
410
+ Waits for an image to appear on screen and clicks on it.
411
+
412
+ Convenience function for waiting for elements that may take time to appear.
413
+ """
414
+ try:
415
+ return self.click_image(
416
+ image_label=image_label,
417
+ images_folder=images_folder,
418
+ confidence=confidence,
419
+ timeout=timeout,
420
+ search_interval=1.0, # Longer interval for waiting
421
+ )
422
+ except Exception as e:
423
+ ArtemisError(f"Error waiting and clicking: {str(e)}.") from e
424
+
425
+ def quick_click(self,
426
+ image_label: str,
427
+ images_folder: Union[str, Path] = "resource"
428
+ ) -> Union[Tuple[int, int], bool]:
429
+ """
430
+ Quick click with optimized default settings.
431
+
432
+ Convenience function for fast clicks with balanced settings.
433
+ """
434
+
435
+ try:
436
+ return self.click_image(
437
+ image_label=image_label,
438
+ images_folder=images_folder,
439
+ confidence=0.8,
440
+ timeout=3.0,
441
+ search_interval=0.2,
442
+ grayscale=True, # Faster
443
+ )
444
+ except Exception as e:
445
+ ArtemisError(f"Error performing quick click: {str(e)}.") from e
@@ -10,6 +10,10 @@ from functools import wraps
10
10
 
11
11
  T = TypeVar("T")
12
12
 
13
+ class AsyncRunnerError(Exception):
14
+ """Custom exception for AsyncRunner errors."""
15
+ def __init__(self, message):
16
+ super().__init__(f'AsyncRunnerError: {message}')
13
17
 
14
18
  class AsyncRunner(Generic[T]):
15
19
  """
@@ -19,7 +23,7 @@ class AsyncRunner(Generic[T]):
19
23
  Optimized for I/O bound operations (network, files, etc).
20
24
  """
21
25
 
22
- def __init__(self):
26
+ def __init__(self) -> None:
23
27
  """Start AsyncRunner."""
24
28
  self._task = None
25
29
  self._start_time = None
@@ -45,7 +49,7 @@ class AsyncRunner(Generic[T]):
45
49
 
46
50
  return wrapper
47
51
 
48
- async def _execute_function(self, function, args, kwargs):
52
+ async def _execute_function(self, function, args, kwargs) -> None:
49
53
  """
50
54
  Executes the function and manages results/errors.
51
55
 
@@ -80,14 +84,17 @@ class AsyncRunner(Generic[T]):
80
84
  Returns:
81
85
  self: Returns the instance itself.
82
86
  """
83
- self._result.clear()
84
- self._start_time = time.time()
87
+ try:
88
+ self._result.clear()
89
+ self._start_time = time.time()
85
90
 
86
- # Creates and schedules the asynchronous task
87
- loop = asyncio.get_event_loop()
88
- self._task = loop.create_task(self._execute_function(function, args, kwargs))
91
+ # Creates and schedules the asynchronous task
92
+ loop = asyncio.get_event_loop()
93
+ self._task = loop.create_task(self._execute_function(function, args, kwargs))
89
94
 
90
- return self
95
+ return self
96
+ except Exception as e:
97
+ AsyncRunnerError(f"Erro ao iniciar a execução da função: {str(e)}.") from e
91
98
 
92
99
  def is_running(self) -> bool:
93
100
  """