clerk-sdk 0.4.14__py3-none-any.whl → 0.4.16__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.
- clerk/__init__.py +1 -1
- clerk/base.py +1 -1
- clerk/gui_automation/client.py +5 -0
- clerk/gui_automation/client_actor/client_actor.py +18 -2
- clerk/gui_automation/decorators/gui_automation.py +31 -68
- {clerk_sdk-0.4.14.dist-info → clerk_sdk-0.4.16.dist-info}/METADATA +1 -1
- {clerk_sdk-0.4.14.dist-info → clerk_sdk-0.4.16.dist-info}/RECORD +10 -10
- {clerk_sdk-0.4.14.dist-info → clerk_sdk-0.4.16.dist-info}/WHEEL +0 -0
- {clerk_sdk-0.4.14.dist-info → clerk_sdk-0.4.16.dist-info}/licenses/LICENSE +0 -0
- {clerk_sdk-0.4.14.dist-info → clerk_sdk-0.4.16.dist-info}/top_level.txt +0 -0
clerk/__init__.py
CHANGED
clerk/base.py
CHANGED
|
@@ -50,7 +50,7 @@ class BaseClerk(BaseModel):
|
|
|
50
50
|
headers: Dict[str, str] = {},
|
|
51
51
|
json: Dict[str, Any] = {},
|
|
52
52
|
params: Dict[str, Any] = {},
|
|
53
|
-
) -> StandardResponse[
|
|
53
|
+
) -> StandardResponse[Any]:
|
|
54
54
|
|
|
55
55
|
merged_headers = {**self.headers, **headers}
|
|
56
56
|
url = f"{self.base_url}{endpoint}"
|
clerk/gui_automation/client.py
CHANGED
|
@@ -40,6 +40,11 @@ class RPAClerk(BaseClerk):
|
|
|
40
40
|
json={"id": remote_device.id, "name": remote_device.name, "run_id": run_id},
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
+
def get_wss_token(self) -> str:
|
|
44
|
+
endpoint = "/wss_token"
|
|
45
|
+
res = self.get_request(endpoint=endpoint)
|
|
46
|
+
return res.data[0]
|
|
47
|
+
|
|
43
48
|
def get_coordinates(self, payload: Dict[str, Any]) -> Coords:
|
|
44
49
|
endpoint = "/action_model/get_coordinates"
|
|
45
50
|
try:
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
+
import backoff
|
|
4
5
|
from typing import Any, Dict, Union
|
|
5
6
|
|
|
7
|
+
|
|
6
8
|
import pydantic
|
|
7
9
|
import requests
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
from websockets import WebSocketException
|
|
10
12
|
from .model import (
|
|
11
13
|
ExecutePayload,
|
|
12
14
|
DeleteFilesExecutePayload,
|
|
@@ -15,12 +17,24 @@ from .model import (
|
|
|
15
17
|
WindowExecutePayload,
|
|
16
18
|
GetFileExecutePayload,
|
|
17
19
|
)
|
|
18
|
-
import backoff
|
|
19
20
|
|
|
20
21
|
from .model import PerformActionResponse, ActionStates
|
|
21
22
|
from .exception import PerformActionException, GetScreenError
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
async def before_retry(details: Any):
|
|
26
|
+
from ..decorators.gui_automation import reconnect_ws
|
|
27
|
+
|
|
28
|
+
await reconnect_ws()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@backoff.on_exception(
|
|
32
|
+
backoff.constant,
|
|
33
|
+
WebSocketException,
|
|
34
|
+
interval=1,
|
|
35
|
+
max_tries=5,
|
|
36
|
+
on_backoff=before_retry,
|
|
37
|
+
)
|
|
24
38
|
async def _perform_action_ws(payload: Dict[str, Any]) -> PerformActionResponse:
|
|
25
39
|
"""Perform an action over a WebSocket connection.
|
|
26
40
|
|
|
@@ -48,6 +62,8 @@ async def _perform_action_ws(payload: Dict[str, Any]) -> PerformActionResponse:
|
|
|
48
62
|
return PerformActionResponse(**json.loads(action_info))
|
|
49
63
|
else:
|
|
50
64
|
raise RuntimeError("Received ACK != OK")
|
|
65
|
+
except WebSocketException as e:
|
|
66
|
+
raise e
|
|
51
67
|
except asyncio.TimeoutError:
|
|
52
68
|
raise RuntimeError("The ack message did not arrive.")
|
|
53
69
|
else:
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
import functools
|
|
3
3
|
import os
|
|
4
4
|
import time
|
|
5
|
-
from typing import Callable, Union
|
|
5
|
+
from typing import Any, Callable, Dict, Sequence, Union
|
|
6
6
|
|
|
7
7
|
from websockets.asyncio.client import connect, ClientConnection
|
|
8
8
|
from websockets.protocol import State
|
|
@@ -24,54 +24,30 @@ global_ws: Union[ClientConnection, None] = None
|
|
|
24
24
|
clerk_client = RPAClerk()
|
|
25
25
|
wss_uri = "wss://agent-manager.f-one.group/action"
|
|
26
26
|
|
|
27
|
-
REMOTE_DEVICE_ALLOCATION_TIMEOUT = int(
|
|
28
|
-
os.getenv("REMOTE_DEVICE_ALLOCATION_TIMEOUT", 60)
|
|
29
|
-
)
|
|
30
|
-
REMOTE_DEVICE_ALLOCATION_MAX_TRIES = int(
|
|
31
|
-
os.getenv("REMOTE_DEVICE_ALLOCATION_MAX_TRIES", 60)
|
|
32
|
-
)
|
|
33
27
|
|
|
28
|
+
async def connect_to_ws(uri: str) -> ClientConnection:
|
|
29
|
+
# Same knobs as before, just via the new connect()
|
|
30
|
+
return await connect(uri, max_size=2**23, ping_timeout=3600)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def close_ws_connection(ws_conn: ClientConnection):
|
|
34
|
+
await ws_conn.close()
|
|
34
35
|
|
|
35
|
-
def _allocate_remote_device(
|
|
36
|
-
clerk_client: RPAClerk, group_name: str, run_id: str
|
|
37
|
-
) -> RemoteDevice:
|
|
38
|
-
remote_device = None
|
|
39
|
-
retries = 0
|
|
40
|
-
|
|
41
|
-
while True:
|
|
42
|
-
try:
|
|
43
|
-
remote_device = clerk_client.allocate_remote_device(
|
|
44
|
-
group_name=group_name, run_id=run_id
|
|
45
|
-
)
|
|
46
|
-
os.environ["REMOTE_DEVICE_ID"] = remote_device.id
|
|
47
|
-
os.environ["REMOTE_DEVICE_NAME"] = remote_device.name
|
|
48
|
-
logger.debug(f"Remote device allocated: {remote_device.name}")
|
|
49
|
-
return remote_device
|
|
50
|
-
|
|
51
|
-
except NoClientsAvailable:
|
|
52
|
-
logger.warning(
|
|
53
|
-
f"No clients are available for {group_name} group. Initiating a {REMOTE_DEVICE_ALLOCATION_TIMEOUT} seconds wait. Retry count: {retries}"
|
|
54
|
-
)
|
|
55
|
-
if retries >= REMOTE_DEVICE_ALLOCATION_MAX_TRIES:
|
|
56
|
-
raise ClientAvailabilityTimeout(
|
|
57
|
-
f"No clients available for {group_name} group after {REMOTE_DEVICE_ALLOCATION_TIMEOUT * REMOTE_DEVICE_ALLOCATION_MAX_TRIES} seconds"
|
|
58
|
-
)
|
|
59
|
-
time.sleep(REMOTE_DEVICE_ALLOCATION_TIMEOUT)
|
|
60
|
-
retries += 1
|
|
61
36
|
|
|
37
|
+
async def reconnect_ws():
|
|
38
|
+
global global_ws
|
|
62
39
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
remote_device_name = os.getenv("REMOTE_DEVICE_NAME")
|
|
41
|
+
if not remote_device_name:
|
|
42
|
+
raise RuntimeError(
|
|
43
|
+
"REMOTE_DEVICE_NAME environmental variable is required for reconnecting WebSocket."
|
|
44
|
+
)
|
|
45
|
+
wss_token = clerk_client.get_wss_token()
|
|
46
|
+
uri = f"{wss_uri}/{remote_device_name}/publisher?token={wss_token}"
|
|
47
|
+
global_ws = await connect_to_ws(uri)
|
|
70
48
|
|
|
71
49
|
|
|
72
|
-
def gui_automation(
|
|
73
|
-
reserve_client: bool = False,
|
|
74
|
-
):
|
|
50
|
+
def gui_automation():
|
|
75
51
|
"""
|
|
76
52
|
Decorator that:
|
|
77
53
|
• Allocates a remote device,
|
|
@@ -79,28 +55,22 @@ def gui_automation(
|
|
|
79
55
|
• Passes control to the wrapped function,
|
|
80
56
|
• Cleans everything up afterwards.
|
|
81
57
|
"""
|
|
82
|
-
|
|
83
|
-
if not group_name:
|
|
84
|
-
raise ValueError("REMOTE_DEVICE_GROUP environmental variable is required.")
|
|
58
|
+
remote_device_name: str | None = os.getenv("REMOTE_DEVICE_NAME")
|
|
85
59
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return await connect(uri, max_size=2**23, ping_timeout=3600)
|
|
60
|
+
if not remote_device_name:
|
|
61
|
+
raise ValueError("REMOTE_DEVICE_NAME environmental variable is required.")
|
|
89
62
|
|
|
90
|
-
|
|
91
|
-
await ws_conn.close()
|
|
63
|
+
wss_token = clerk_client.get_wss_token()
|
|
92
64
|
|
|
93
65
|
def decorator(func: Callable):
|
|
94
66
|
@functools.wraps(func)
|
|
95
|
-
def wrapper(
|
|
67
|
+
def wrapper(
|
|
68
|
+
payload: ClerkCodePayload, *args: Sequence[Any], **kwargs: Dict[str, Any]
|
|
69
|
+
):
|
|
96
70
|
global global_ws
|
|
97
|
-
force_deallocate = False
|
|
98
|
-
os.environ["_document_id"] = payload.document.id
|
|
99
|
-
os.environ["_run_id"] = payload.run_id
|
|
100
71
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
72
|
+
os.environ["_document_id"] = payload.document.id
|
|
73
|
+
os.environ["_run_id"] = payload.run_id or ""
|
|
104
74
|
|
|
105
75
|
# Create a dedicated loop for the WebSocket work
|
|
106
76
|
event_loop = asyncio.new_event_loop()
|
|
@@ -109,8 +79,8 @@ def gui_automation(
|
|
|
109
79
|
try:
|
|
110
80
|
task = event_loop.create_task(
|
|
111
81
|
connect_to_ws(
|
|
112
|
-
f"{wss_uri}/{
|
|
113
|
-
f"?token={
|
|
82
|
+
f"{wss_uri}/{remote_device_name}/publisher"
|
|
83
|
+
f"?token={wss_token}"
|
|
114
84
|
)
|
|
115
85
|
)
|
|
116
86
|
global_ws = event_loop.run_until_complete(task)
|
|
@@ -122,18 +92,11 @@ def gui_automation(
|
|
|
122
92
|
global_ws = None
|
|
123
93
|
raise WebSocketConnectionFailed()
|
|
124
94
|
|
|
125
|
-
except Exception
|
|
126
|
-
force_deallocate = True
|
|
95
|
+
except Exception:
|
|
127
96
|
raise
|
|
128
97
|
finally:
|
|
129
98
|
os.environ.pop("_run_id", None)
|
|
130
99
|
os.environ.pop("_document_id", None)
|
|
131
|
-
if not reserve_client or force_deallocate:
|
|
132
|
-
_deallocate_target(clerk_client, remote_device, payload.run_id)
|
|
133
|
-
else:
|
|
134
|
-
logger.warning(
|
|
135
|
-
f"The client stayed reserved for the this run id: {payload.run_id}"
|
|
136
|
-
)
|
|
137
100
|
|
|
138
101
|
if global_ws and global_ws.state is State.OPEN:
|
|
139
102
|
close_task = event_loop.create_task(close_ws_connection(global_ws))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
clerk/__init__.py,sha256=
|
|
2
|
-
clerk/base.py,sha256=
|
|
1
|
+
clerk/__init__.py,sha256=Wg-ijr3ORGg_AUwKn5_55Orq5wZkcW4xEKwGliI4PmI,51
|
|
2
|
+
clerk/base.py,sha256=lbFTdpdDfsmYIQUFH93S1aw0-L6GNJwAcubW1tdMFX4,3967
|
|
3
3
|
clerk/client.py,sha256=RdOvC23WK9ZtIXDOYaoSFk9debh3UTmstBXjAswAH6E,4981
|
|
4
4
|
clerk/decorator/__init__.py,sha256=yGGcS17VsZ7cZ-hVGCm3I3vGDJMiJIAqmDGzriIi0DI,65
|
|
5
5
|
clerk/decorator/models.py,sha256=nFMdVSG3nJ4hrEXs9YbI_GgjHbVjhSWZokOCzUh-lqQ,521
|
|
@@ -8,16 +8,16 @@ clerk/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
8
8
|
clerk/exceptions/exceptions.py,sha256=gSCma06b6W6c0NrA2rhzd5YjFhZGa6caShX07lo-_3E,1291
|
|
9
9
|
clerk/exceptions/remote_device.py,sha256=R0Vfmk8ejibEFeV29A8Vqst2YA6yxdqEX9lP5wWubgM,134
|
|
10
10
|
clerk/gui_automation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
clerk/gui_automation/client.py,sha256=
|
|
11
|
+
clerk/gui_automation/client.py,sha256=lLIim3i9PkdPlt5vmV4u2GoqQ571PHy7EvmRtTBv-JQ,5516
|
|
12
12
|
clerk/gui_automation/action_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
clerk/gui_automation/action_model/model.py,sha256=yzaCyEMOH3YMkPBf6IwUMuu69-xyf78HzmthiewgWQY,3811
|
|
14
14
|
clerk/gui_automation/action_model/utils.py,sha256=xzFxgN-bTK6HKGS7J-esQZ-ePj_yG72T-2ZVhcWvKjw,798
|
|
15
15
|
clerk/gui_automation/client_actor/__init__.py,sha256=SVuL6-oo1Xc0oJkjMKrO6mJwpPGjrCLKhDV6r2Abtf8,66
|
|
16
|
-
clerk/gui_automation/client_actor/client_actor.py,sha256=
|
|
16
|
+
clerk/gui_automation/client_actor/client_actor.py,sha256=D2mQAHitF5x-dZzt70ZPw_fZGwuwWTAAwuHXbdKVKfE,5475
|
|
17
17
|
clerk/gui_automation/client_actor/exception.py,sha256=zdnImHZ88yf52Xq3aMHivEU3aJg-r2c-r8x8XZnI3ic,407
|
|
18
18
|
clerk/gui_automation/client_actor/model.py,sha256=wVpFCi1w2kh4kAV8oNx489vf_SLUQnqhc02rFD5NIJA,6335
|
|
19
19
|
clerk/gui_automation/decorators/__init__.py,sha256=OCgXStEumscgT-RyVy5OKS7ml1w9y-lEnjCVnxuRnQs,43
|
|
20
|
-
clerk/gui_automation/decorators/gui_automation.py,sha256=
|
|
20
|
+
clerk/gui_automation/decorators/gui_automation.py,sha256=CwfDmTP9d4BOEsQS4_YGwOoz1gbDwYsA-JVnXz25Cto,3680
|
|
21
21
|
clerk/gui_automation/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
clerk/gui_automation/exceptions/agent_manager.py,sha256=KFWFWQ7X_8Z9XG__SMzb1jKI3yNoVTPJAXzW5O-fSXc,101
|
|
23
23
|
clerk/gui_automation/exceptions/websocket.py,sha256=-MdwSwlf1hbnu55aDgk3L1znkTZ6xJS6po5VpEGD9ck,117
|
|
@@ -45,8 +45,8 @@ clerk/models/ui_operator.py,sha256=mKTJUFZgv7PeEt5oys28HVZxHOJsofmRQOcRpqj0dbU,2
|
|
|
45
45
|
clerk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
clerk/utils/logger.py,sha256=NrMIlJfVmRjjRw_N_Jngkl0qqv7btXUbg5wxcRmFEH4,3800
|
|
47
47
|
clerk/utils/save_artifact.py,sha256=94aYkYNVGcSUaSWZmdjiY6Oc-3yCKb2XWCZ56IAXQqk,1158
|
|
48
|
-
clerk_sdk-0.4.
|
|
49
|
-
clerk_sdk-0.4.
|
|
50
|
-
clerk_sdk-0.4.
|
|
51
|
-
clerk_sdk-0.4.
|
|
52
|
-
clerk_sdk-0.4.
|
|
48
|
+
clerk_sdk-0.4.16.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
|
|
49
|
+
clerk_sdk-0.4.16.dist-info/METADATA,sha256=ofSEyVDVWXdyuQCKmh2WwMeMHwGptTRtLnXnK1oFAns,9937
|
|
50
|
+
clerk_sdk-0.4.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
51
|
+
clerk_sdk-0.4.16.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
|
|
52
|
+
clerk_sdk-0.4.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|