clerk-sdk 0.3.7__tar.gz → 0.4.0__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 (63) hide show
  1. {clerk_sdk-0.3.7/clerk_sdk.egg-info → clerk_sdk-0.4.0}/PKG-INFO +1 -1
  2. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/base.py +47 -13
  3. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/client.py +19 -2
  4. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/decorator/__init__.py +2 -0
  5. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/decorator/models.py +2 -2
  6. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/decorator/task_decorator.py +11 -5
  7. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/exceptions/exceptions.py +7 -7
  8. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/action_model/model.py +1 -1
  9. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/client.py +16 -12
  10. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/client_actor/client_actor.py +3 -3
  11. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/client_actor/model.py +16 -13
  12. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/decorators/gui_automation.py +1 -1
  13. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_actions/actions.py +24 -24
  14. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_actions/base.py +5 -2
  15. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_inspector/gui_vision.py +2 -2
  16. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_inspector/models.py +5 -5
  17. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/models.py +4 -4
  18. clerk_sdk-0.4.0/clerk/models/document.py +81 -0
  19. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/file.py +4 -2
  20. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0/clerk_sdk.egg-info}/PKG-INFO +1 -1
  21. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/setup.py +1 -1
  22. clerk_sdk-0.3.7/clerk/models/document.py +0 -20
  23. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/LICENSE +0 -0
  24. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/MANIFEST.in +0 -0
  25. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/README.md +0 -0
  26. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/__init__.py +0 -0
  27. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/exceptions/__init__.py +0 -0
  28. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/exceptions/remote_device.py +0 -0
  29. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/__init__.py +0 -0
  30. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/action_model/__init__.py +0 -0
  31. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/action_model/utils.py +0 -0
  32. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/client_actor/__init__.py +0 -0
  33. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/client_actor/exception.py +0 -0
  34. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/decorators/__init__.py +0 -0
  35. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/exceptions/__init__.py +0 -0
  36. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/exceptions/agent_manager.py +0 -0
  37. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/exceptions/modality/__init__.py +0 -0
  38. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/exceptions/modality/exc.py +0 -0
  39. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/exceptions/websocket.py +0 -0
  40. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/requirements.txt +0 -0
  41. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_actions/__init__.py +0 -0
  42. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_actions/support.py +0 -0
  43. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
  44. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/__init__.py +0 -0
  45. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/ai_recovery.py +0 -0
  46. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/decorators.py +0 -0
  47. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/exceptions.py +0 -0
  48. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/gui_automation/ui_state_machine/state_machine.py +0 -0
  49. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/__init__.py +0 -0
  50. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/document_statuses.py +0 -0
  51. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/remote_device.py +0 -0
  52. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/response_model.py +0 -0
  53. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/models/ui_operator.py +0 -0
  54. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/utils/__init__.py +0 -0
  55. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/utils/logger.py +0 -0
  56. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk/utils/save_artifact.py +0 -0
  57. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk_sdk.egg-info/SOURCES.txt +0 -0
  58. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk_sdk.egg-info/dependency_links.txt +0 -0
  59. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk_sdk.egg-info/requires.txt +0 -0
  60. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/clerk_sdk.egg-info/top_level.txt +0 -0
  61. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/pyproject.toml +0 -0
  62. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/requirements.txt +0 -0
  63. {clerk_sdk-0.3.7 → clerk_sdk-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clerk-sdk
3
- Version: 0.3.7
3
+ Version: 0.4.0
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
@@ -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
 
@@ -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"]
@@ -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
@@ -16,14 +16,14 @@ def clerk_code():
16
16
  def wrapper(payload: Optional[ClerkCodePayload] = None) -> ClerkCodePayload:
17
17
  # 1. Load payload from file if not provided
18
18
  use_pickle = False
19
- output = None
19
+ output: ClerkCodePayload | BaseException | None = None
20
20
  error_occurred = False
21
21
  error_info = None
22
22
  if payload is None:
23
23
  use_pickle = True
24
- # Write a placeholder output file in case of early failure
24
+ # Write a placeholder output file in case of early failure
25
25
  with open(output_pkl, "wb") as f:
26
- pickle.dump({"error": "Early failure"}, f)
26
+ pickle.dump({"error": "Early failure"}, f)
27
27
  try:
28
28
  with open(input_pkl, "rb") as f:
29
29
  raw_data = pickle.load(f)
@@ -40,11 +40,13 @@ def clerk_code():
40
40
  traceback=traceback.format_exc(),
41
41
  )
42
42
 
43
+ assert payload is not None
44
+
43
45
  # 2. Execute function
44
46
  if not error_occurred:
45
47
  try:
46
48
  output = func(payload)
47
- if not isinstance(output, ClerkCodePayload):
49
+ if not isinstance(output, ClerkCodePayload): # type: ignore
48
50
  raise TypeError(
49
51
  "Function must return a ClerkCodePayload instance."
50
52
  )
@@ -58,7 +60,7 @@ def clerk_code():
58
60
 
59
61
  # 3. write to output.pkl
60
62
  try:
61
- if use_pickle:
63
+ if use_pickle and output is not None:
62
64
  with open(output_pkl, "wb") as f:
63
65
  if error_occurred:
64
66
  pickle.dump(error_info, f)
@@ -66,6 +68,7 @@ def clerk_code():
66
68
  pickle.dump(output, f)
67
69
  else:
68
70
  pickle.dump(output.model_dump(mode="json"), f)
71
+
69
72
  except Exception as e:
70
73
  # If writing output.pkl fails, try to write a minimal error
71
74
  try:
@@ -85,6 +88,9 @@ def clerk_code():
85
88
  if isinstance(output, Exception):
86
89
  raise output
87
90
 
91
+ if output is None:
92
+ raise RuntimeError("output object cannot be None")
93
+
88
94
  return output
89
95
 
90
96
  return wrapper
@@ -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):
@@ -0,0 +1,81 @@
1
+ from datetime import datetime
2
+ import mimetypes
3
+ import os
4
+ from typing import Any, Dict, List, Optional
5
+ from pydantic import BaseModel
6
+
7
+ from clerk.models.document_statuses import DocumentStatuses
8
+ from clerk.models.file import ParsedFile
9
+
10
+
11
+ class Document(BaseModel):
12
+ id: str
13
+ project_id: str
14
+ title: str
15
+ upload_date: datetime
16
+ requestor: Optional[str] = None
17
+ message_subject: Optional[str] = None
18
+ message_content: Optional[str] = None
19
+ message_html: Optional[str] = None
20
+ structured_data: Optional[Dict[str, Any]] = None
21
+ status: DocumentStatuses
22
+ created_at: datetime
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()
@@ -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.0
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
@@ -13,7 +13,7 @@ gui_requirements = get_requirements("./clerk/gui_automation")
13
13
 
14
14
  setup(
15
15
  name="clerk-sdk",
16
- version="0.3.7",
16
+ version="0.4.0",
17
17
  description="Library for interacting with Clerk",
18
18
  long_description=open("README.md").read(),
19
19
  long_description_content_type="text/markdown",
@@ -1,20 +0,0 @@
1
- import datetime
2
- from typing import Dict, Optional
3
- from pydantic import BaseModel
4
-
5
- from clerk.models.document_statuses import DocumentStatuses
6
-
7
-
8
- class Document(BaseModel):
9
- id: str
10
- project_id: str
11
- title: str
12
- upload_date: datetime
13
- requestor: Optional[str] = None
14
- message_subject: Optional[str] = None
15
- message_content: Optional[str] = None
16
- message_html: Optional[str] = None
17
- structured_data: Optional[Dict] = None
18
- status: DocumentStatuses
19
- created_at: datetime
20
- updated_at: datetime
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes