clerk-sdk 0.2.0__py3-none-any.whl → 0.2.2__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.
@@ -3,6 +3,7 @@ from typing import Dict, List, Optional
3
3
  from pydantic import BaseModel
4
4
  from clerk.base import BaseClerk
5
5
  from clerk.gui_automation.action_model.model import Coords
6
+ from clerk.gui_automation.exceptions.agent_manager import NoClientsAvailable
6
7
  from clerk.gui_automation.ui_state_inspector.models import (
7
8
  ActionString,
8
9
  BaseState,
@@ -22,6 +23,9 @@ class RPAClerk(BaseClerk):
22
23
  endpoint=endpoint, json={"group_name": group_name, "run_id": run_id}
23
24
  )
24
25
 
26
+ if res.data[0] is None:
27
+ raise NoClientsAvailable()
28
+
25
29
  return RemoteDevice(**res.data[0])
26
30
 
27
31
  def deallocate_remote_device(
@@ -68,9 +68,7 @@ async def _get_screen_async() -> str:
68
68
  payload = {
69
69
  "proc_inst_id": os.getenv("PROC_ID"),
70
70
  "client_name": os.getenv("REMOTE_DEVICE_NAME"),
71
- "headless": (
72
- True if os.getenv("HEADLESS", "True").lower() == "true" else False
73
- ),
71
+ "headless": True,
74
72
  "action": {"action_type": "screenshot"},
75
73
  }
76
74
  try:
@@ -1,15 +1,20 @@
1
1
  import asyncio
2
2
  import functools
3
- import logging
4
3
  import os
4
+ import time
5
5
  from typing import Callable, Union
6
6
 
7
7
  from websockets.asyncio.client import connect, ClientConnection
8
8
  from websockets.protocol import State
9
9
 
10
10
  from clerk.gui_automation.client import RPAClerk
11
+ from clerk.gui_automation.exceptions.agent_manager import (
12
+ ClientAvailabilityTimeout,
13
+ NoClientsAvailable,
14
+ )
11
15
  from clerk.models.remote_device import RemoteDevice
12
16
  from clerk.decorator.models import ClerkCodePayload
17
+ from clerk.utils import logger
13
18
  from ..exceptions.websocket import WebSocketConnectionFailed
14
19
 
15
20
 
@@ -19,22 +24,43 @@ global_ws: Union[ClientConnection, None] = None
19
24
  clerk_client = RPAClerk()
20
25
  wss_uri = "wss://agent-manager.f-one.group/action"
21
26
 
27
+ CLIENT_TIMEOUT_MINS = 1
28
+ MAX_CLIENT_AVAILABILITY_RETRIES = 60
29
+
22
30
 
23
31
  def _allocate_remote_device(
24
32
  clerk_client: RPAClerk, group_name: str, run_id: str
25
33
  ) -> RemoteDevice:
26
- remote_device = clerk_client.allocate_remote_device(
27
- group_name=group_name, run_id=run_id
28
- )
29
- os.environ["REMOTE_DEVICE_ID"] = remote_device.id
30
- os.environ["REMOTE_DEVICE_NAME"] = remote_device.name
31
- return remote_device
34
+ remote_device = None
35
+ retries = 0
36
+
37
+ while True:
38
+ try:
39
+ remote_device = clerk_client.allocate_remote_device(
40
+ group_name=group_name, run_id=run_id
41
+ )
42
+ os.environ["REMOTE_DEVICE_ID"] = remote_device.id
43
+ os.environ["REMOTE_DEVICE_NAME"] = remote_device.name
44
+ logger.debug(f"Remote device allocated: {remote_device.name}")
45
+ return remote_device
46
+
47
+ except NoClientsAvailable:
48
+ logger.warning(
49
+ f"No clients are available for {group_name} group. Initiating a {CLIENT_TIMEOUT_MINS} minute wait. Retry count: {retries}"
50
+ )
51
+ retries += 1
52
+ if retries == MAX_CLIENT_AVAILABILITY_RETRIES:
53
+ raise ClientAvailabilityTimeout(
54
+ f"No clients available for {group_name} group after {CLIENT_TIMEOUT_MINS*MAX_CLIENT_AVAILABILITY_RETRIES} minutes"
55
+ )
56
+ time.sleep(CLIENT_TIMEOUT_MINS * 60)
32
57
 
33
58
 
34
59
  def _deallocate_target(
35
60
  clerk_client: RPAClerk, remote_device: RemoteDevice, run_id: str
36
61
  ):
37
62
  clerk_client.deallocate_remote_device(remote_device=remote_device, run_id=run_id)
63
+ logger.debug("Remote device deallocated")
38
64
  os.environ.pop("REMOTE_DEVICE_ID", None)
39
65
  os.environ.pop("REMOTE_DEVICE_NAME", None)
40
66
 
@@ -82,7 +108,7 @@ def gui_automation():
82
108
  global_ws = event_loop.run_until_complete(task)
83
109
 
84
110
  if global_ws and global_ws.state is State.OPEN:
85
- logging.debug("WebSocket connection established.")
111
+ logger.debug("WebSocket connection established.")
86
112
  func_ret = func(payload, *args, **kwargs)
87
113
  else:
88
114
  global_ws = None
@@ -97,7 +123,7 @@ def gui_automation():
97
123
  if global_ws and global_ws.state is State.OPEN:
98
124
  close_task = event_loop.create_task(close_ws_connection(global_ws))
99
125
  event_loop.run_until_complete(close_task)
100
- print("WebSocket connection closed.")
126
+ logger.debug("WebSocket connection closed.")
101
127
 
102
128
  event_loop.run_until_complete(event_loop.shutdown_asyncgens())
103
129
  event_loop.close()
@@ -0,0 +1,6 @@
1
+ class NoClientsAvailable(Exception):
2
+ pass
3
+
4
+
5
+ class ClientAvailabilityTimeout(Exception):
6
+ pass
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import base64
2
3
  from typing import Optional
3
4
  from backoff._typing import Details
4
5
 
@@ -48,7 +49,7 @@ def save_screenshot(filename: str, sub_folder: Optional[str] = None) -> str:
48
49
  screen_b64: str = get_screen()
49
50
  return save_artifact(
50
51
  filename=filename,
51
- file_bytes=screen_b64.encode("utf-8"),
52
+ file_bytes=base64.b64decode(screen_b64),
52
53
  subfolder=sub_folder,
53
54
  )
54
55
 
clerk/utils/logger.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  import sys
5
5
 
6
6
  if sys.platform == "win32":
7
- base_path = os.path.join(os.getcwd(), "data", "output", "artifacts")
7
+ base_path = os.path.join(os.getcwd(), "data", "artifacts")
8
8
  else:
9
9
  base_path = "/app/data/artifacts"
10
10
 
@@ -100,7 +100,13 @@ def _log(level: str, message: str):
100
100
  formatter = logging.Formatter(format)
101
101
  fh.setFormatter(formatter)
102
102
  logger.addHandler(fh)
103
+ # → console handler (same formatter)
104
+ sh = logging.StreamHandler(sys.stdout)
105
+ sh.setFormatter(formatter)
106
+ logger.addHandler(sh)
107
+
103
108
  logger.setLevel(logging.DEBUG)
109
+ logger.propagate = False # keep root logger from printing duplicates
104
110
 
105
111
  # Log the message based on the level
106
112
  if level.lower() == "info":
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from typing import Optional
3
+ from clerk.utils import logger
3
4
 
4
5
 
5
6
  def save_artifact(
@@ -32,4 +33,5 @@ def save_artifact(
32
33
  with open(file_path, "wb") as f:
33
34
  f.write(file_bytes)
34
35
 
36
+ logger.debug(f"Artifact successfully saved at: {file_path}")
35
37
  return file_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clerk-sdk
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Library for interacting with Clerk
5
5
  Home-page: https://github.com/F-ONE-Group/clerk_pypi
6
6
  Author: F-ONE Group
@@ -5,24 +5,25 @@ clerk/decorator/__init__.py,sha256=4VCGOFNq7pA7iJS410V_R9eWfjxP7mwxwa4thwaUTf8,3
5
5
  clerk/decorator/models.py,sha256=JWFOJuUh3F40iOL0aoNgUC9GRHU3Fcq3GOjSXA7Gwng,394
6
6
  clerk/decorator/task_decorator.py,sha256=srb8AC3tNj1RWC4iNkkwkUuG33B8TtxfTpOm1Oi373Y,2169
7
7
  clerk/gui_automation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- clerk/gui_automation/client.py,sha256=FFK1xXGEvz3ZuUnmgvibhDIvdrOcBpa6pG-Z1cqzhKc,4256
8
+ clerk/gui_automation/client.py,sha256=LJFHdWreH0pZnRUZJ6jd4Xp3kYJjOjNLEUfHD0ly2Y0,4405
9
9
  clerk/gui_automation/action_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  clerk/gui_automation/action_model/model.py,sha256=sbTySgOq4Xv_r2p7q_ji1bGCHgRS1HXC9Z39thwCQ9c,3829
11
11
  clerk/gui_automation/action_model/utils.py,sha256=xzFxgN-bTK6HKGS7J-esQZ-ePj_yG72T-2ZVhcWvKjw,798
12
12
  clerk/gui_automation/client_actor/__init__.py,sha256=SVuL6-oo1Xc0oJkjMKrO6mJwpPGjrCLKhDV6r2Abtf8,66
13
- clerk/gui_automation/client_actor/client_actor.py,sha256=5hQ2KPLnXueUu6tOq-w44dDm3_Lxx4nA_mMnAZmqfVY,5167
13
+ clerk/gui_automation/client_actor/client_actor.py,sha256=Me3NHc0nMA7Y5CUcnE0ZHT1P7dQRGR6duCAKwn0Tg6g,5081
14
14
  clerk/gui_automation/client_actor/exception.py,sha256=zdnImHZ88yf52Xq3aMHivEU3aJg-r2c-r8x8XZnI3ic,407
15
15
  clerk/gui_automation/client_actor/model.py,sha256=wVpFCi1w2kh4kAV8oNx489vf_SLUQnqhc02rFD5NIJA,6335
16
16
  clerk/gui_automation/decorators/__init__.py,sha256=OCgXStEumscgT-RyVy5OKS7ml1w9y-lEnjCVnxuRnQs,43
17
- clerk/gui_automation/decorators/gui_automation.py,sha256=AUDawosvwTXpIT0jLKzrgVUG0Fqu_Qs_AhAm3zgRfIo,3706
17
+ clerk/gui_automation/decorators/gui_automation.py,sha256=C7s1HekmLiXvw3I7O3oCrrTAAlxb_w3PA7YAILGhSLc,4711
18
18
  clerk/gui_automation/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ clerk/gui_automation/exceptions/agent_manager.py,sha256=KFWFWQ7X_8Z9XG__SMzb1jKI3yNoVTPJAXzW5O-fSXc,101
19
20
  clerk/gui_automation/exceptions/websocket.py,sha256=-MdwSwlf1hbnu55aDgk3L1znkTZ6xJS6po5VpEGD9ck,117
20
21
  clerk/gui_automation/exceptions/modality/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  clerk/gui_automation/exceptions/modality/exc.py,sha256=P-dMuCTyVZYD3pbGpCf_1SYEgaETn13c51pmfbsXr5k,1436
22
23
  clerk/gui_automation/ui_actions/__init__.py,sha256=-EDQ5375HXrvG3sfFY7zOPC405YcBL6xXRACm2p-YyI,23
23
24
  clerk/gui_automation/ui_actions/actions.py,sha256=hhxl5VMDNSXdqm2L0tZqs6IhJHVXtlSVSdwsiz2BbDI,27449
24
25
  clerk/gui_automation/ui_actions/base.py,sha256=4CjOkMZNLt8W0tLO91tOkz8CCea0m2IRBIOpWNMOZUA,7230
25
- clerk/gui_automation/ui_actions/support.py,sha256=oPu_xNOQb6i83pSDQ9if14Jh-noT2q0o-Rl3y5DrHLE,2160
26
+ clerk/gui_automation/ui_actions/support.py,sha256=BS9QReyP_F9-oGPt0fuy5BD9kC9-E5mOE05Zk0M1Hzw,2176
26
27
  clerk/gui_automation/ui_state_inspector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
28
  clerk/gui_automation/ui_state_inspector/gui_vision.py,sha256=lRnF3IMLViOB-UE447A4kc8BO7NKqqLV1NZKRswRKWg,7707
28
29
  clerk/gui_automation/ui_state_inspector/models.py,sha256=uuzYhE9is4Z-TmsVMA1mwf0mQGs_PDLqdSZKbe8dHSs,5691
@@ -39,10 +40,10 @@ clerk/models/file.py,sha256=7n6bV6ukRA-uh7MFxUx_TEMNpVAdNe5O0nKIeneCQhg,459
39
40
  clerk/models/remote_device.py,sha256=2jijS-9WWq7y6xIbAaDaPnzZo3-tjp2-dCQvNKP8YSU,109
40
41
  clerk/models/response_model.py,sha256=R62daUN1YVOwgnrh_epvFRsQcOwT7R4u97l73egvm-c,232
41
42
  clerk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
- clerk/utils/logger.py,sha256=OxpWOo7IMBajp6rwkZH8-7E9VxRNzJNfxC8fDFClDVo,3425
43
- clerk/utils/save_artifact.py,sha256=Y4lxcGKxr-5tKdY66Z4BghI4p0Vk4LtEBNsh6Nh01zc,1021
44
- clerk_sdk-0.2.0.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
45
- clerk_sdk-0.2.0.dist-info/METADATA,sha256=9p4HdIoMfDoXlQX6rT0e624wqy2bGxAlhS1PJ0L02fQ,3508
46
- clerk_sdk-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- clerk_sdk-0.2.0.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
48
- clerk_sdk-0.2.0.dist-info/RECORD,,
43
+ clerk/utils/logger.py,sha256=_jYOSEc1WCsY9DvU8cIWUPmz41QNjpHTEZ7WrUnmFPA,3650
44
+ clerk/utils/save_artifact.py,sha256=Lwl3l3QqKnSFLFpfEEa-dJ-dV9OxvCU3ADkuYZ18U-Q,1117
45
+ clerk_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
46
+ clerk_sdk-0.2.2.dist-info/METADATA,sha256=8VZysSvY_q_eoLR6DQ7MetvLforSTw82vDUbGTAXfJI,3508
47
+ clerk_sdk-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ clerk_sdk-0.2.2.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
49
+ clerk_sdk-0.2.2.dist-info/RECORD,,