clerk-sdk 0.3.7__py3-none-any.whl → 0.4.1__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/base.py CHANGED
@@ -2,7 +2,7 @@ import os
2
2
 
3
3
  import requests
4
4
  import backoff
5
- from typing import Dict, Optional, Self
5
+ from typing import Any, Dict, List, Optional, Self, Tuple
6
6
 
7
7
 
8
8
  from pydantic import BaseModel, model_validator, Field
@@ -10,7 +10,7 @@ from pydantic import BaseModel, model_validator, Field
10
10
  from .models.response_model import StandardResponse
11
11
 
12
12
 
13
- def giveup_handler(e):
13
+ def giveup_handler(e: Any):
14
14
  return (
15
15
  isinstance(e, requests.exceptions.HTTPError)
16
16
  and e.response is not None
@@ -42,16 +42,15 @@ class BaseClerk(BaseModel):
42
42
  (requests.exceptions.RequestException,),
43
43
  max_tries=3,
44
44
  jitter=None,
45
- # on_backoff=backoff_handler,
46
45
  giveup=giveup_handler,
47
46
  )
48
47
  def get_request(
49
48
  self,
50
49
  endpoint: str,
51
50
  headers: Dict[str, str] = {},
52
- json: Dict = {},
53
- params: Dict = {},
54
- ) -> StandardResponse:
51
+ json: Dict[str, Any] = {},
52
+ params: Dict[str, Any] = {},
53
+ ) -> StandardResponse[Dict[str, Any]]:
55
54
 
56
55
  merged_headers = {**self.headers, **headers}
57
56
  url = f"{self.base_url}{endpoint}"
@@ -70,18 +69,17 @@ class BaseClerk(BaseModel):
70
69
  (requests.exceptions.RequestException,),
71
70
  max_tries=3,
72
71
  jitter=None,
73
- # on_backoff=backoff_handler,
74
72
  giveup=giveup_handler,
75
73
  )
76
74
  def post_request(
77
75
  self,
78
76
  endpoint: str,
79
77
  headers: Dict[str, str] = {},
80
- json: Dict = {},
81
- params: Dict = {},
82
- data: Dict = None,
83
- files: Dict = {},
84
- ) -> StandardResponse:
78
+ json: Dict[str, Any] = {},
79
+ params: Dict[str, Any] = {},
80
+ data: Dict[str, Any] | None = None,
81
+ files: List[Tuple[str, Tuple[str, bytes, str | None]]] | None = None,
82
+ ) -> StandardResponse[Dict[str, Any]]:
85
83
 
86
84
  merged_headers = {**self.headers, **headers}
87
85
  url = f"{self.base_url}{endpoint}"
@@ -96,7 +94,43 @@ class BaseClerk(BaseModel):
96
94
  json=json,
97
95
  params=params,
98
96
  data=data,
99
- files=files,
97
+ files=files, # type: ignore
98
+ )
99
+ response.raise_for_status()
100
+
101
+ return StandardResponse(**response.json())
102
+
103
+ @backoff.on_exception(
104
+ backoff.expo,
105
+ (requests.exceptions.RequestException,),
106
+ max_tries=3,
107
+ jitter=None,
108
+ giveup=giveup_handler,
109
+ )
110
+ def put_request(
111
+ self,
112
+ endpoint: str,
113
+ headers: Dict[str, str] = {},
114
+ json: Dict[str, Any] = {},
115
+ params: Dict[str, Any] = {},
116
+ data: Dict[str, Any] | None = None,
117
+ files: List[Tuple[str, Tuple[str, bytes, str | None]]] | None = None,
118
+ ) -> StandardResponse[Dict[str, Any]]:
119
+
120
+ merged_headers = {**self.headers, **headers}
121
+ url = f"{self.base_url}{endpoint}"
122
+ if self.root_endpoint:
123
+ url = f"{self.base_url}{self.root_endpoint}{endpoint}"
124
+
125
+ # logger.info(f"POST {url} | body={json} | params={params}")
126
+
127
+ response = requests.put(
128
+ url,
129
+ headers=merged_headers,
130
+ json=json,
131
+ params=params,
132
+ data=data,
133
+ files=files, # type: ignore
100
134
  )
101
135
  response.raise_for_status()
102
136
 
clerk/client.py CHANGED
@@ -1,11 +1,28 @@
1
- from typing import List, Literal
2
- from xml.dom.minidom import Document
1
+ from typing import Any, Dict, List, Literal
3
2
 
4
3
  from clerk.base import BaseClerk
4
+ from clerk.models.document import Document, UploadDocumentRequest
5
5
  from .models.file import ParsedFile, UploadFile
6
6
 
7
7
 
8
8
  class Clerk(BaseClerk):
9
+
10
+ def upload_document(self, request: UploadDocumentRequest) -> Document:
11
+ endpoint = "/document"
12
+ res = self.post_request(
13
+ endpoint=endpoint, data=request.data, files=request.files_
14
+ )
15
+ return Document(**res.data[0])
16
+
17
+ def update_document_structured_data(
18
+ self, document_id: str, updated_structured_data: Dict[str, Any]
19
+ ) -> Document:
20
+ endpoint = f"/document/{document_id}"
21
+ payload = dict(structured_data=updated_structured_data)
22
+ res = self.put_request(endpoint, json=payload)
23
+
24
+ return Document(**res.data[0])
25
+
9
26
  def get_document(self, document_id: str) -> Document:
10
27
  endpoint = f"/document/{document_id}"
11
28
  res = self.get_request(endpoint=endpoint)
@@ -1 +1,3 @@
1
1
  from .task_decorator import clerk_code
2
+
3
+ __all__ = ["clerk_code"]
clerk/decorator/models.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional
1
+ from typing import Any, Dict, List, Optional
2
2
  from pydantic import BaseModel
3
3
 
4
4
 
@@ -16,5 +16,5 @@ class Document(BaseModel):
16
16
 
17
17
  class ClerkCodePayload(BaseModel):
18
18
  document: Document
19
- structured_data: Dict
19
+ structured_data: Dict[str, Any]
20
20
  run_id: Optional[str] = None
@@ -13,17 +13,19 @@ output_pkl: str = "/app/data/output/output.pkl"
13
13
  def clerk_code():
14
14
  def decorator(func: Callable[[ClerkCodePayload], ClerkCodePayload]):
15
15
  @wraps(func)
16
- def wrapper(payload: Optional[ClerkCodePayload] = None) -> ClerkCodePayload:
16
+ def wrapper(
17
+ payload: Optional[ClerkCodePayload] = None,
18
+ ) -> ClerkCodePayload | None:
17
19
  # 1. Load payload from file if not provided
18
20
  use_pickle = False
19
- output = None
21
+ output: ClerkCodePayload | BaseException | None = None
20
22
  error_occurred = False
21
23
  error_info = None
22
24
  if payload is None:
23
25
  use_pickle = True
24
- # Write a placeholder output file in case of early failure
26
+ # Write a placeholder output file in case of early failure
25
27
  with open(output_pkl, "wb") as f:
26
- pickle.dump({"error": "Early failure"}, f)
28
+ pickle.dump({"error": "Early failure"}, f)
27
29
  try:
28
30
  with open(input_pkl, "rb") as f:
29
31
  raw_data = pickle.load(f)
@@ -40,11 +42,13 @@ def clerk_code():
40
42
  traceback=traceback.format_exc(),
41
43
  )
42
44
 
45
+ assert payload is not None
46
+
43
47
  # 2. Execute function
44
48
  if not error_occurred:
45
49
  try:
46
50
  output = func(payload)
47
- if not isinstance(output, ClerkCodePayload):
51
+ if not isinstance(output, ClerkCodePayload): # type: ignore
48
52
  raise TypeError(
49
53
  "Function must return a ClerkCodePayload instance."
50
54
  )
@@ -55,10 +59,11 @@ def clerk_code():
55
59
  message=str(e),
56
60
  traceback=traceback.format_exc(),
57
61
  )
62
+ output = error_info
58
63
 
59
64
  # 3. write to output.pkl
60
65
  try:
61
- if use_pickle:
66
+ if use_pickle and output is not None:
62
67
  with open(output_pkl, "wb") as f:
63
68
  if error_occurred:
64
69
  pickle.dump(error_info, f)
@@ -66,6 +71,7 @@ def clerk_code():
66
71
  pickle.dump(output, f)
67
72
  else:
68
73
  pickle.dump(output.model_dump(mode="json"), f)
74
+
69
75
  except Exception as e:
70
76
  # If writing output.pkl fails, try to write a minimal error
71
77
  try:
@@ -1,10 +1,10 @@
1
- from typing import Optional
1
+ from typing import Any, Optional
2
2
 
3
3
 
4
4
  class AppBaseException(Exception):
5
5
  def __init__(
6
6
  self,
7
- *args,
7
+ *args: Any,
8
8
  type_: Optional[str] = None,
9
9
  message: Optional[str] = None,
10
10
  traceback: Optional[str] = None,
@@ -23,17 +23,17 @@ class AppBaseException(Exception):
23
23
  self.traceback = traceback
24
24
 
25
25
  # (Optional) make pickling round-trip the extra fields explicitly
26
- def __reduce__(self):
26
+ def __reduce__(self): # type: ignore
27
27
  # Reconstruct with message-only (what Exception expects) and restore extras via state
28
28
  return (
29
29
  self.__class__,
30
30
  (self.message,),
31
31
  {"type": self.type, "traceback": self.traceback},
32
- )
32
+ ) # type: ignore
33
33
 
34
- def __setstate__(self, state):
35
- for k, v in state.items():
36
- setattr(self, k, v)
34
+ def __setstate__(self, state): # type: ignore
35
+ for k, v in state.items(): # type: ignore
36
+ setattr(self, k, v) # type: ignore
37
37
 
38
38
 
39
39
  class ApplicationException(AppBaseException):
@@ -1,7 +1,7 @@
1
1
  import base64
2
2
  import os
3
3
  from typing import List, Literal, Optional, Union
4
- from pydantic import BaseModel, Field, validator
4
+ from pydantic import BaseModel
5
5
 
6
6
  CoordsType = Union[List[float], List[int]]
7
7
 
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional
1
+ from typing import Any, Dict, List, Optional, Type
2
2
 
3
3
  from pydantic import BaseModel
4
4
  from clerk.base import BaseClerk
@@ -16,7 +16,7 @@ from clerk.models.ui_operator import UiOperatorTask
16
16
 
17
17
  class RPAClerk(BaseClerk):
18
18
 
19
- root_endpoint: str = "/gui_automation"
19
+ root_endpoint = "/gui_automation"
20
20
 
21
21
  def allocate_remote_device(self, group_name: str, run_id: str):
22
22
  endpoint = "/remote_device/allocate"
@@ -24,7 +24,7 @@ class RPAClerk(BaseClerk):
24
24
  endpoint=endpoint, json={"group_name": group_name, "run_id": run_id}
25
25
  )
26
26
 
27
- if res.data[0] is None:
27
+ if res.data[0] is None: # type: ignore
28
28
  raise NoClientsAvailable()
29
29
 
30
30
  return RemoteDevice(**res.data[0])
@@ -40,14 +40,14 @@ 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_coordinates(self, payload: Dict) -> Coords:
43
+ def get_coordinates(self, payload: Dict[str, Any]) -> Coords:
44
44
  endpoint = "/action_model/get_coordinates"
45
45
  res = self.post_request(endpoint=endpoint, json=payload)
46
- if res.data[0] is None:
46
+ if res.data[0] is None: # type: ignore
47
47
  raise RuntimeError("No coordinates found in the response.")
48
48
  return Coords(**res.data[0])
49
49
 
50
- def create_ui_operator_task(self, payload: Dict) -> UiOperatorTask:
50
+ def create_ui_operator_task(self, payload: Dict[str, Any]) -> UiOperatorTask:
51
51
  endpoint = "/ui_operator"
52
52
  res = self.post_request(endpoint=endpoint, json=payload)
53
53
  return UiOperatorTask(**res.data[0])
@@ -59,7 +59,7 @@ class RPAClerk(BaseClerk):
59
59
 
60
60
 
61
61
  class GUIVisionClerk(BaseClerk):
62
- root_endpoint: str = "/gui_automation/vision"
62
+ root_endpoint = "/gui_automation/vision"
63
63
 
64
64
  def find_target(self, screen_b64: str, use_ocr: bool, target_prompt: str):
65
65
  endpoint = "/find_target"
@@ -89,8 +89,12 @@ class GUIVisionClerk(BaseClerk):
89
89
  return BaseState(**res.data[0])
90
90
 
91
91
  def answer(
92
- self, screen_b64: str, use_ocr: bool, question: str, output_model: BaseModel
93
- ) -> Dict:
92
+ self,
93
+ screen_b64: str,
94
+ use_ocr: bool,
95
+ question: str,
96
+ output_model: Type[BaseModel],
97
+ ) -> BaseModel:
94
98
  endpoint = "/answer"
95
99
  res = self.post_request(
96
100
  endpoint=endpoint,
@@ -102,7 +106,7 @@ class GUIVisionClerk(BaseClerk):
102
106
  },
103
107
  )
104
108
 
105
- return output_model(**res.data[0])
109
+ return output_model.model_validate(**res.data[0])
106
110
 
107
111
  def classify_state(
108
112
  self, screen_b64: str, use_ocr: bool, possible_states: List[Dict[str, str]]
@@ -136,12 +140,12 @@ class GUIVisionClerk(BaseClerk):
136
140
 
137
141
 
138
142
  class CourseCorrectorClerk(BaseClerk):
139
- root_endpoint: str = "/gui_automation/course_corrector"
143
+ root_endpoint = "/gui_automation/course_correction"
140
144
 
141
145
  def get_corrective_actions(
142
146
  self,
143
147
  screen_b64: str,
144
- use_ocr: str,
148
+ use_ocr: bool,
145
149
  goal: str,
146
150
  custom_instructions: Optional[str] = None,
147
151
  ) -> ActionString:
@@ -21,7 +21,7 @@ from .model import PerformActionResponse, ActionStates
21
21
  from .exception import PerformActionException, GetScreenError
22
22
 
23
23
 
24
- async def _perform_action_ws(payload: Dict) -> PerformActionResponse:
24
+ async def _perform_action_ws(payload: Dict[str, Any]) -> PerformActionResponse:
25
25
  """Perform an action over a WebSocket connection.
26
26
 
27
27
  Args:
@@ -65,7 +65,7 @@ async def _get_screen_async() -> str:
65
65
  This function sends a request to perform a screenshot action over a WebSocket connection
66
66
  and returns the base64 encoded image of the screen captured.
67
67
  """
68
- payload = {
68
+ payload: Dict[str, Any] = {
69
69
  "proc_inst_id": os.getenv("_run_id"),
70
70
  "client_name": os.getenv("REMOTE_DEVICE_NAME"),
71
71
  "headless": True,
@@ -131,7 +131,7 @@ async def _perform_action_async(
131
131
  Raises:
132
132
  PerformActionException: If the action fails with an error message.
133
133
  """
134
- req_payload: Dict = {
134
+ req_payload: Dict[str, Any] = {
135
135
  "proc_inst_id": os.getenv("_run_id"),
136
136
  "client_name": os.getenv("REMOTE_DEVICE_NAME"),
137
137
  "headless": (
@@ -1,20 +1,22 @@
1
- from typing import Any, List, Literal, Optional, Union
1
+ from typing import Any, List, Literal, Optional, TypeAlias, Union
2
2
  from pydantic import BaseModel, Field
3
3
  from enum import Enum
4
4
 
5
+ from clerk.gui_automation.ui_actions.base import ActionTypes
5
6
 
6
- ActionTypes = Literal[
7
- "left_click",
8
- "right_click",
9
- "middle_click",
10
- "double_click",
11
- "send_keys",
12
- "press_keys",
13
- "hot_keys",
14
- "paste_text",
15
- "get_text",
16
- "scroll",
17
- ]
7
+
8
+ # ActionTypes = Literal[
9
+ # "left_click",
10
+ # "right_click",
11
+ # "middle_click",
12
+ # "double_click",
13
+ # "send_keys",
14
+ # "press_keys",
15
+ # "hot_keys",
16
+ # "paste_text",
17
+ # "get_text",
18
+ # "scroll",
19
+ # ]
18
20
 
19
21
 
20
22
  class ActionStates(Enum):
@@ -77,6 +79,7 @@ class WindowExecutePayload(BaseModel):
77
79
  "close_window",
78
80
  "activate_window",
79
81
  ]
82
+
80
83
  window_name: str
81
84
  timeout: int = Field(default=10)
82
85
 
@@ -79,7 +79,7 @@ def gui_automation(
79
79
  • Passes control to the wrapped function,
80
80
  • Cleans everything up afterwards.
81
81
  """
82
- group_name: str = os.getenv("REMOTE_DEVICE_GROUP")
82
+ group_name: str | None = os.getenv("REMOTE_DEVICE_GROUP")
83
83
  if not group_name:
84
84
  raise ValueError("REMOTE_DEVICE_GROUP environmental variable is required.")
85
85
 
@@ -45,7 +45,7 @@ class File(BaseModel):
45
45
 
46
46
  @field_validator("content", mode="before")
47
47
  @classmethod
48
- def convert_to_bytes(cls, v) -> bytes:
48
+ def convert_to_bytes(cls, v: str | bytes) -> bytes:
49
49
  if isinstance(v, str):
50
50
  from base64 import b64decode
51
51
 
@@ -75,7 +75,7 @@ class LeftClick(BaseAction):
75
75
  LeftClick(target="Suche").above("Kalender").do()
76
76
  """
77
77
 
78
- action_type: Literal["left_click"] = "left_click"
78
+ action_type = "left_click"
79
79
 
80
80
  @backoff.on_exception(
81
81
  backoff.expo,
@@ -100,7 +100,7 @@ class LeftClick(BaseAction):
100
100
 
101
101
  @property
102
102
  def actionable_string(self):
103
- return f"LeftClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
103
+ return f"LeftClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}').do()"
104
104
 
105
105
 
106
106
  class RightClick(BaseAction):
@@ -118,7 +118,7 @@ class RightClick(BaseAction):
118
118
  RightClick(target="Suche").above("Kalender").do()
119
119
  """
120
120
 
121
- action_type: Literal["right_click"] = "right_click"
121
+ action_type = "right_click"
122
122
 
123
123
  @backoff.on_exception(
124
124
  backoff.expo,
@@ -143,7 +143,7 @@ class RightClick(BaseAction):
143
143
 
144
144
  @property
145
145
  def actionable_string(self):
146
- return f"RightClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
146
+ return f"RightClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}').do()"
147
147
 
148
148
 
149
149
  class MiddleClickAction(BaseAction):
@@ -161,7 +161,7 @@ class MiddleClickAction(BaseAction):
161
161
  MiddleClickAction(target="Suche").above("Kalender").do()
162
162
  """
163
163
 
164
- action_type: Literal["middle_click"] = "middle_click"
164
+ action_type = "middle_click"
165
165
 
166
166
  @backoff.on_exception(
167
167
  backoff.expo,
@@ -186,7 +186,7 @@ class MiddleClickAction(BaseAction):
186
186
 
187
187
  @property
188
188
  def actionable_string(self):
189
- return f"MiddleClickAction(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
189
+ return f"MiddleClickAction(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}').do()"
190
190
 
191
191
 
192
192
  class DoubleClick(BaseAction):
@@ -204,7 +204,7 @@ class DoubleClick(BaseAction):
204
204
  DoubleClick(target="Suche").above("Kalender").do()
205
205
  """
206
206
 
207
- action_type: Literal["double_click"] = "double_click"
207
+ action_type = "double_click"
208
208
 
209
209
  @backoff.on_exception(
210
210
  backoff.expo,
@@ -229,7 +229,7 @@ class DoubleClick(BaseAction):
229
229
 
230
230
  @property
231
231
  def actionable_string(self):
232
- return f"DoubleClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
232
+ return f"DoubleClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}').do()"
233
233
 
234
234
 
235
235
  class Scroll(BaseAction):
@@ -249,7 +249,7 @@ class Scroll(BaseAction):
249
249
  DoubleClick(target="Suche").above("Kalender").do()
250
250
  """
251
251
 
252
- action_type: Literal["scroll"] = "scroll"
252
+ action_type = "scroll"
253
253
  clicks: int
254
254
  click_coords: List[int] = Field(default=[])
255
255
 
@@ -333,7 +333,7 @@ class SendKeys(BaseAction):
333
333
 
334
334
  @property
335
335
  def actionable_string(self):
336
- return f"SendKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', keys='{self.keys}').do()"
336
+ return f"SendKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}', keys='{self.keys}').do()"
337
337
 
338
338
 
339
339
  class PressKeys(BaseAction):
@@ -353,7 +353,7 @@ class PressKeys(BaseAction):
353
353
  PressKeys(keys='ctrl+shift+esc').do()
354
354
  """
355
355
 
356
- action_type: ActionTypes = "press_keys"
356
+ action_type = "press_keys"
357
357
  keys: str
358
358
 
359
359
  def do(self):
@@ -366,7 +366,7 @@ class PressKeys(BaseAction):
366
366
 
367
367
  @property
368
368
  def actionable_string(self):
369
- return f"PressKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', keys='{self.keys}').do()"
369
+ return f"PressKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchors}', keys='{self.keys}').do()"
370
370
 
371
371
 
372
372
  class WaitFor(BaseAction):
@@ -385,7 +385,7 @@ class WaitFor(BaseAction):
385
385
  WaitFor("element").do(timeout=60)
386
386
  """
387
387
 
388
- action_type: Literal["wait_for"] = "wait_for"
388
+ action_type = "wait_for"
389
389
  retry_timeout: float = 0.5
390
390
  is_awaited: bool = True
391
391
 
@@ -450,7 +450,7 @@ class OpenApplication(BaseAction):
450
450
  OpenApplication(app_path="/path/to/application.exe", app_window_name="Application Window").do()
451
451
  """
452
452
 
453
- action_type: Literal["open_app"] = "open_app"
453
+ action_type = "open_app"
454
454
  app_path: str = Field(description="Absolute path of the application")
455
455
  app_window_name: str = Field(
456
456
  description="Name of the application window once open. Wildcard logic enabled."
@@ -479,7 +479,7 @@ class ForceCloseApplication(BaseAction):
479
479
  Executes the action to force close the application by creating and performing an ApplicationExecutePayload with the specified process name.
480
480
  """
481
481
 
482
- action_type: Literal["force_close_app"] = "force_close_app"
482
+ action_type = "force_close_app"
483
483
  process_name: str = Field(
484
484
  description="Process name from task manager. Example: process.exe"
485
485
  )
@@ -509,7 +509,7 @@ class SaveFiles(BaseAction):
509
509
  SaveFiles(save_location="/path/to/", files=["/path/to/file_1", "/path/to/file_2"]).do()
510
510
  """
511
511
 
512
- action_type: ActionTypes = "save_files"
512
+ action_type = "save_files"
513
513
  save_location: str
514
514
  files: Union[List[str], List[FileDetails]]
515
515
 
@@ -580,7 +580,7 @@ class GetFile(BaseAction):
580
580
  GetFile(file_location="/path/to/file_1").do()
581
581
  """
582
582
 
583
- action_type: Literal["get_file"] = "get_file"
583
+ action_type = "get_file"
584
584
  file_location: str
585
585
 
586
586
  def do(self) -> File:
@@ -605,7 +605,7 @@ class MaximizeWindow(BaseAction):
605
605
  MaximizeWindow(window_name="MyWindow").do()
606
606
  """
607
607
 
608
- action_type: Literal["maximize_window"] = "maximize_window"
608
+ action_type = "maximize_window"
609
609
  window_name: str
610
610
 
611
611
  def do(self, timeout: int = 10):
@@ -630,7 +630,7 @@ class MinimizeWindow(BaseAction):
630
630
  MinimizeWindow(window_name="MyWindow").do()
631
631
  """
632
632
 
633
- action_type: Literal["minimize_window"] = "minimize_window"
633
+ action_type = "minimize_window"
634
634
  window_name: str
635
635
 
636
636
  def do(self, timeout: int = 10):
@@ -655,7 +655,7 @@ class CloseWindow(BaseAction):
655
655
  CloseWindow(window_name="MyWindow").do()
656
656
  """
657
657
 
658
- action_type: Literal["close_window"] = "close_window"
658
+ action_type = "close_window"
659
659
  window_name: str
660
660
 
661
661
  def do(self, timeout: int = 10):
@@ -680,7 +680,7 @@ class ActivateWindow(BaseAction):
680
680
  ActivateWindow(window_name="MyWindow").do()
681
681
  """
682
682
 
683
- action_type: Literal["activate_window"] = "activate_window"
683
+ action_type = "activate_window"
684
684
  window_name: str
685
685
 
686
686
  def do(self, timeout: int = 10):
@@ -709,7 +709,7 @@ class GetText(BaseAction):
709
709
 
710
710
  """
711
711
 
712
- action_type: Literal["get_text"] = "get_text"
712
+ action_type = "get_text"
713
713
 
714
714
  @backoff.on_exception(
715
715
  backoff.expo,
@@ -752,7 +752,7 @@ class PasteText(BaseAction):
752
752
 
753
753
  """
754
754
 
755
- action_type: Literal["paste_text"] = "paste_text"
755
+ action_type = "paste_text"
756
756
  keys: Union[str, List[str]]
757
757
  followed_by: Optional[str] = Field(default=None)
758
758
 
@@ -1,4 +1,4 @@
1
- from typing import Literal, Self, Union, List, Optional
1
+ from typing import Literal, Self, TypeAlias, Union, List, Optional
2
2
  from pydantic import BaseModel, Field, model_validator
3
3
  from ..client_actor import get_screen
4
4
  from ..exceptions.modality.exc import TargetModalityError
@@ -24,12 +24,13 @@ def to_full_img_path(img: Union[str, ImageB64]) -> str:
24
24
  return os.path.join(TARGET_IMAGES_PATH, img)
25
25
 
26
26
 
27
- ActionTypes = Literal[
27
+ ActionTypes: TypeAlias = Literal[
28
28
  "left_click",
29
29
  "right_click",
30
30
  "middle_click",
31
31
  "double_click",
32
32
  "send_keys",
33
+ "hot_keys",
33
34
  "press_keys",
34
35
  "wait_for",
35
36
  "open_app",
@@ -130,6 +131,8 @@ class BaseAction(BaseModel):
130
131
  return [xcenter, ycenter]
131
132
 
132
133
  def _prepare_payload(self):
134
+ if not self.target:
135
+ raise ValueError("target cannot be None.")
133
136
  payload: Screenshot = Screenshot(
134
137
  screen_b64=ImageB64(value=get_screen()),
135
138
  target=self.target,
@@ -115,7 +115,7 @@ class Vision(BaseModel):
115
115
  The current state of the GUI (BaseState or a subclass of BaseState)
116
116
  """
117
117
  screen_b64 = get_screen()
118
- state = self.verify_state(
118
+ state = self.clerk_client.verify_state(
119
119
  screen_b64,
120
120
  self.use_ocr,
121
121
  possible_states,
@@ -160,7 +160,7 @@ class Vision(BaseModel):
160
160
  screen_b64, self.use_ocr, possible_states
161
161
  )
162
162
  # if output_model is provided, return the model, otherwise return the id and description of the default model
163
- if output_model is not None:
163
+ if output_model is not None: # type: ignore
164
164
  return state
165
165
  assert isinstance(state, BaseState)
166
166
  return state.id, state.description
@@ -1,5 +1,5 @@
1
- from pydantic import BaseModel, field_validator, model_validator
2
- from typing import List, Dict, Optional
1
+ from pydantic import BaseModel, field_validator
2
+ from typing import Any, List, Dict, Optional
3
3
 
4
4
 
5
5
  class BaseState(BaseModel):
@@ -21,7 +21,7 @@ class BaseState(BaseModel):
21
21
 
22
22
  id: str
23
23
  description: str
24
- screenshots: List = []
24
+ screenshots: List[Dict[str, str]] = []
25
25
 
26
26
  def add_screenshot(self, bucket_name: str, file_name: str):
27
27
  self.screenshots.append({"bucket_name": bucket_name, "file_name": file_name})
@@ -137,7 +137,7 @@ class TargetWithAnchor(BaseModel):
137
137
 
138
138
  @field_validator("target", mode="before")
139
139
  @classmethod
140
- def retain_one_word(cls, v):
140
+ def retain_one_word(cls, v: str):
141
141
  return v.split(" ")[-1]
142
142
 
143
143
 
@@ -174,7 +174,7 @@ class ActionString(BaseModel):
174
174
 
175
175
  @field_validator("action_string", mode="before")
176
176
  @classmethod
177
- def ensure_format(cls, v):
177
+ def ensure_format(cls, v: Any):
178
178
  if not isinstance(v, str):
179
179
  raise ValueError("Action string must be a string")
180
180
  if not v.startswith("LeftClick") and not v.startswith("NoAction"):
@@ -1,5 +1,5 @@
1
- from typing import Optional
2
- from pydantic import BaseModel, field_validator, model_validator
1
+ from typing import Any, Optional
2
+ from pydantic import BaseModel, field_validator
3
3
 
4
4
 
5
5
  class ActionString(BaseModel):
@@ -24,7 +24,7 @@ class ActionString(BaseModel):
24
24
 
25
25
  @field_validator("action_string", mode="before")
26
26
  @classmethod
27
- def ensure_format(cls, v):
27
+ def ensure_format(cls, v: Any):
28
28
  if not isinstance(v, str):
29
29
  raise ValueError("Action string must be a string")
30
30
  if not v.endswith(".do()") and not v.startswith("NoAction"):
@@ -32,7 +32,7 @@ class ActionString(BaseModel):
32
32
  return v
33
33
 
34
34
  @field_validator("interrupt_process", mode="before")
35
- def convert_to_bool(cls, v):
35
+ def convert_to_bool(cls, v: str | bool | None):
36
36
  if v is None:
37
37
  return False
38
38
  elif isinstance(v, str):
clerk/models/document.py CHANGED
@@ -1,8 +1,11 @@
1
- import datetime
2
- from typing import Dict, Optional
1
+ from datetime import datetime
2
+ import mimetypes
3
+ import os
4
+ from typing import Any, Dict, List, Optional
3
5
  from pydantic import BaseModel
4
6
 
5
7
  from clerk.models.document_statuses import DocumentStatuses
8
+ from clerk.models.file import ParsedFile
6
9
 
7
10
 
8
11
  class Document(BaseModel):
@@ -14,7 +17,65 @@ class Document(BaseModel):
14
17
  message_subject: Optional[str] = None
15
18
  message_content: Optional[str] = None
16
19
  message_html: Optional[str] = None
17
- structured_data: Optional[Dict] = None
20
+ structured_data: Optional[Dict[str, Any]] = None
18
21
  status: DocumentStatuses
19
22
  created_at: datetime
20
23
  updated_at: datetime
24
+
25
+
26
+ class UploadDocumentRequest(BaseModel):
27
+ project_id: str
28
+ message_subject: Optional[str] = None
29
+ message_content: Optional[str] = None
30
+ files: List[str | ParsedFile] = []
31
+
32
+ def _define_files(self):
33
+ formatted_files: List[
34
+ tuple[
35
+ str,
36
+ tuple[
37
+ str,
38
+ bytes,
39
+ str | None,
40
+ ],
41
+ ]
42
+ ] = []
43
+
44
+ for file in self.files:
45
+ if isinstance(file, str):
46
+ if os.path.exists(file):
47
+ tmp = (
48
+ "files",
49
+ (
50
+ os.path.basename(file).replace(" ", "_"),
51
+ open(file, "rb").read(),
52
+ mimetypes.guess_type(file)[0],
53
+ ),
54
+ )
55
+
56
+ else:
57
+ raise FileExistsError(file)
58
+ else:
59
+ tmp = (
60
+ "files",
61
+ (
62
+ file.name,
63
+ file.decoded_content,
64
+ file.mimetype,
65
+ ),
66
+ )
67
+ formatted_files.append(tmp)
68
+
69
+ return formatted_files
70
+
71
+ @property
72
+ def data(self) -> Dict[str, Any]:
73
+ return dict(
74
+ project_id=self.project_id,
75
+ message_subject=self.message_subject,
76
+ mesasge_content=self.message_content,
77
+ )
78
+
79
+ @property
80
+ def files_(self):
81
+ return self._define_files()
clerk/models/file.py CHANGED
@@ -18,8 +18,10 @@ class ParsedFile(BaseModel):
18
18
 
19
19
  class UploadFile(BaseModel):
20
20
  name: str
21
- mimetype: str
21
+ mimetype: str | None
22
22
  content: bytes
23
23
 
24
- def to_multipart_format(self, key: str = "files") -> Tuple:
24
+ def to_multipart_format(
25
+ self, key: str = "files"
26
+ ) -> Tuple[str, Tuple[str, bytes, str | None]]:
25
27
  return (key, (self.name, self.content, self.mimetype))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clerk-sdk
3
- Version: 0.3.7
3
+ Version: 0.4.1
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
@@ -1,53 +1,53 @@
1
1
  clerk/__init__.py,sha256=LWpbImG7352mUJYC1tRm_zsn5rnt4sFl5ldoq5f0dlo,26
2
- clerk/base.py,sha256=QpneNQlMms_uUOIMnQ_8KMz8U2hhu7rhUz5xWfa9CCs,2829
3
- clerk/client.py,sha256=TAtn0jwzzfc1WGhiZBFpGRQoNBaLHIMspXmyOdM-abI,973
4
- clerk/decorator/__init__.py,sha256=4VCGOFNq7pA7iJS410V_R9eWfjxP7mwxwa4thwaUTf8,39
5
- clerk/decorator/models.py,sha256=JWFOJuUh3F40iOL0aoNgUC9GRHU3Fcq3GOjSXA7Gwng,394
6
- clerk/decorator/task_decorator.py,sha256=RarLfGR0uDtIBWlaPSIvGhIi2gmHPa4_825RBAL3DgI,3610
2
+ clerk/base.py,sha256=S1RKc2pBw2FPlVjefJzsNtyTDPB0UG46C2K_QVV1opA,4008
3
+ clerk/client.py,sha256=cgDOO9fRfJAoV5_r6PCoPWoAY0OwoeEMT3cEkzUSZOI,1612
4
+ clerk/decorator/__init__.py,sha256=yGGcS17VsZ7cZ-hVGCm3I3vGDJMiJIAqmDGzriIi0DI,65
5
+ clerk/decorator/models.py,sha256=PiAugPvX1c6BQBWtfhyh5k9Uau2OJNDIx7T1TLz7ZFY,409
6
+ clerk/decorator/task_decorator.py,sha256=H8caRvNvvl-IRwyREP66gBGVM-SpQJ1W7oAFImO-6Jw,3769
7
7
  clerk/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- clerk/exceptions/exceptions.py,sha256=aIrmID62HoQo0pLErfX9NR2nopTyf53BpOu4gtqAy-A,1201
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=LqrUQtC-4pjUJYDDn_s3t073K066j_p0vf4kXLChBc0,4891
11
+ clerk/gui_automation/client.py,sha256=zxZ2QgxXvOEenXz2LpZv9vySJRbVnqY5f1lYr4bTQ9E,5000
12
12
  clerk/gui_automation/action_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- clerk/gui_automation/action_model/model.py,sha256=sbTySgOq4Xv_r2p7q_ji1bGCHgRS1HXC9Z39thwCQ9c,3829
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=YVYBgDQ89NkgdlWSk_AvDtEO_BJe5YIHU5OJK4cUs28,5081
16
+ clerk/gui_automation/client_actor/client_actor.py,sha256=RT5WnvrM37pLpoDd_WZg8sSjBuugqMW_eDLTEkL7kWc,5117
17
17
  clerk/gui_automation/client_actor/exception.py,sha256=zdnImHZ88yf52Xq3aMHivEU3aJg-r2c-r8x8XZnI3ic,407
18
- clerk/gui_automation/client_actor/model.py,sha256=wVpFCi1w2kh4kAV8oNx489vf_SLUQnqhc02rFD5NIJA,6335
18
+ clerk/gui_automation/client_actor/model.py,sha256=B6tYQB0ngY3oM6pWIoJLTi_H9U-hTgQqal7b7rSWkd8,6433
19
19
  clerk/gui_automation/decorators/__init__.py,sha256=OCgXStEumscgT-RyVy5OKS7ml1w9y-lEnjCVnxuRnQs,43
20
- clerk/gui_automation/decorators/gui_automation.py,sha256=7p3xU2biyHN43hgEAGj34QJjHF2SeBDM6FRCx_eqewM,5350
20
+ clerk/gui_automation/decorators/gui_automation.py,sha256=jqzN_iqIq2KvjUU-JoYnUSOf-mst31EKCk8zdMFzGQM,5357
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
24
24
  clerk/gui_automation/exceptions/modality/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  clerk/gui_automation/exceptions/modality/exc.py,sha256=P-dMuCTyVZYD3pbGpCf_1SYEgaETn13c51pmfbsXr5k,1436
26
26
  clerk/gui_automation/ui_actions/__init__.py,sha256=-EDQ5375HXrvG3sfFY7zOPC405YcBL6xXRACm2p-YyI,23
27
- clerk/gui_automation/ui_actions/actions.py,sha256=hhxl5VMDNSXdqm2L0tZqs6IhJHVXtlSVSdwsiz2BbDI,27449
28
- clerk/gui_automation/ui_actions/base.py,sha256=4CjOkMZNLt8W0tLO91tOkz8CCea0m2IRBIOpWNMOZUA,7230
27
+ clerk/gui_automation/ui_actions/actions.py,sha256=qFustr0Wr81Bq6tS9sPqqFMC5zwG5-XmjKy1kFwjSKc,26914
28
+ clerk/gui_automation/ui_actions/base.py,sha256=ymYWZnleywyI2DeaAb_HuDxgoSgfgw0zjXyQGp4Nvqo,7351
29
29
  clerk/gui_automation/ui_actions/support.py,sha256=Ulb8DBfwnrBMaYoMLDgldEy9V--NDUSdhIYXpuODZoU,5772
30
30
  clerk/gui_automation/ui_state_inspector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- clerk/gui_automation/ui_state_inspector/gui_vision.py,sha256=lRnF3IMLViOB-UE447A4kc8BO7NKqqLV1NZKRswRKWg,7707
32
- clerk/gui_automation/ui_state_inspector/models.py,sha256=uuzYhE9is4Z-TmsVMA1mwf0mQGs_PDLqdSZKbe8dHSs,5691
31
+ clerk/gui_automation/ui_state_inspector/gui_vision.py,sha256=Pk5nuFZnp_zNbqSOtndSmgqb6PLeADJfnC-eRIJMDZk,7736
32
+ clerk/gui_automation/ui_state_inspector/models.py,sha256=ieri9ht2X694V5snYShj_9LHX8AtdCzSw5mCsCSLbj0,5705
33
33
  clerk/gui_automation/ui_state_machine/__init__.py,sha256=bTPZsPJkDLCwP2mdtBj4fU7C7ekOh0b-VPRKFEV3bPo,301
34
34
  clerk/gui_automation/ui_state_machine/ai_recovery.py,sha256=3_Gu_RPxta4eXmXWH3WA31c15sJL-dk1m1H2bOFV7so,3772
35
35
  clerk/gui_automation/ui_state_machine/decorators.py,sha256=gBOpIusjsXlA7FEiszDCFKTS6vXx3LBNMz_SQJNkMWg,2134
36
36
  clerk/gui_automation/ui_state_machine/exceptions.py,sha256=9KWg20dnCssMdMu632E0nP5vkndgYNI4cDCDW-vMkQA,1436
37
- clerk/gui_automation/ui_state_machine/models.py,sha256=1fpNYPsff0dyLg1wNJl-xQyMYvy4cSDy4OLxa0ej-0U,1395
37
+ clerk/gui_automation/ui_state_machine/models.py,sha256=oNmfHtjqgRkUAxPnQ8R5-IR-K_FCeg3NU6aqjVSnThI,1407
38
38
  clerk/gui_automation/ui_state_machine/state_machine.py,sha256=u2wJqu5WxmZHkzlIHTgK_icZUw3yv9nNuZkh9IaqmXI,35622
39
39
  clerk/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- clerk/models/document.py,sha256=Bbt3YSvGcOrJJjJBYjbDGXpZIaQAYdq_-uIr2urwy5s,525
40
+ clerk/models/document.py,sha256=m9ccAQdy1G-ZgUT-a44TMgIVAp4he-90uYjVPqwgO98,2193
41
41
  clerk/models/document_statuses.py,sha256=ytTQhgACs2m22qz51_7Ti0IxzbVyl-fl7uF7CnDEyLA,279
42
- clerk/models/file.py,sha256=BSLzcsLugG_F_VxN_OFcKtG20JkZyoGMv_1CAwdNkDM,676
42
+ clerk/models/file.py,sha256=wfMGoNTsIBFxpPUzOTOkUYZ-FGxXg6n2mZhDCBt6Jbc,733
43
43
  clerk/models/remote_device.py,sha256=2jijS-9WWq7y6xIbAaDaPnzZo3-tjp2-dCQvNKP8YSU,109
44
44
  clerk/models/response_model.py,sha256=R62daUN1YVOwgnrh_epvFRsQcOwT7R4u97l73egvm-c,232
45
45
  clerk/models/ui_operator.py,sha256=mKTJUFZgv7PeEt5oys28HVZxHOJsofmRQOcRpqj0dbU,293
46
46
  clerk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  clerk/utils/logger.py,sha256=vHbp-lUK3W3c5fhyPsp05p9LGyDHj95v8XM_XQ_FlLc,3691
48
48
  clerk/utils/save_artifact.py,sha256=94aYkYNVGcSUaSWZmdjiY6Oc-3yCKb2XWCZ56IAXQqk,1158
49
- clerk_sdk-0.3.7.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
50
- clerk_sdk-0.3.7.dist-info/METADATA,sha256=TncEzZuTZ1MufOOlqTAt-B-trTwlZ9BB-ieZvgC2-Go,3508
51
- clerk_sdk-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- clerk_sdk-0.3.7.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
53
- clerk_sdk-0.3.7.dist-info/RECORD,,
49
+ clerk_sdk-0.4.1.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
50
+ clerk_sdk-0.4.1.dist-info/METADATA,sha256=50SHt0qwDSsCh5X8ew-k-NBccXZdCyrJ75EuNK7Cm5o,3508
51
+ clerk_sdk-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ clerk_sdk-0.4.1.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
53
+ clerk_sdk-0.4.1.dist-info/RECORD,,