clerk-sdk 0.4.4__py3-none-any.whl → 0.4.6__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 CHANGED
@@ -1 +1,4 @@
1
1
  from .client import Clerk
2
+
3
+
4
+ __version__ = "0.4.6"
clerk/client.py CHANGED
@@ -14,6 +14,78 @@ class Clerk(BaseClerk):
14
14
  )
15
15
  return Document(**res.data[0])
16
16
 
17
+ def cancel_document_run(self, document_id: str) -> Document:
18
+ endpoint = f"/document/{document_id}/cancel"
19
+ res = self.post_request(endpoint=endpoint)
20
+ return Document(**res.data[0])
21
+
22
+ def update_document_structured_data(
23
+ self, document_id: str, updated_structured_data: Dict[str, Any]
24
+ ) -> Document:
25
+ endpoint = f"/document/{document_id}"
26
+ payload = dict(structured_data=updated_structured_data)
27
+ res = self.put_request(endpoint, json=payload)
28
+
29
+ return Document(**res.data[0])
30
+
31
+ def get_document(self, document_id: str) -> Document:
32
+ endpoint = f"/document/{document_id}"
33
+ res = self.get_request(endpoint=endpoint)
34
+ return Document(**res.data[0])
35
+
36
+ def get_documents(self, request: GetDocumentsRequest) -> List[Document]:
37
+ if not any(
38
+ [
39
+ request.organization_id,
40
+ request.project_id,
41
+ request.start_date,
42
+ request.end_date,
43
+ request.query,
44
+ request.include_statuses,
45
+ ]
46
+ ):
47
+ raise ValueError(
48
+ "At least one query parameter (organization_id, project_id, start_date, end_date, query, or include_statuses) must be provided."
49
+ )
50
+
51
+ endpoint = f"/documents"
52
+ params = request.model_dump(mode="json")
53
+ res = self.get_request(endpoint, params=params)
54
+
55
+ return [Document(**d) for d in res.data]
56
+
57
+ def get_files_document(self, document_id: str) -> List[ParsedFile]:
58
+ endpoint = f"/document/{document_id}/files"
59
+ res = self.get_request(endpoint=endpoint)
60
+ return [ParsedFile(**d) for d in res.data]
61
+
62
+ def add_files_to_document(
63
+ self,
64
+ document_id: str,
65
+ type: Literal["input", "output"],
66
+ files: List[UploadFile],
67
+ ):
68
+ endpoint = f"/document/{document_id}/files/upload"
69
+ params = {"type": type}
70
+ files_data = [f.to_multipart_format() for f in files]
71
+ self.post_request(endpoint, params=params, files=files_data)
72
+
73
+
74
+ class ClerkDocument(BaseClerk):
75
+ endpoint: str = "/document"
76
+
77
+ def upload_document(self, request: UploadDocumentRequest) -> Document:
78
+ endpoint = "/document"
79
+ res = self.post_request(
80
+ endpoint=endpoint, data=request.data, files=request.files_
81
+ )
82
+ return Document(**res.data[0])
83
+
84
+ def cancel_document_run(self, document_id: str) -> Document:
85
+ endpoint = f"/document/{document_id}/cancel"
86
+ res = self.post_request(endpoint=endpoint)
87
+ return Document(**res.data[0])
88
+
17
89
  def update_document_structured_data(
18
90
  self, document_id: str, updated_structured_data: Dict[str, Any]
19
91
  ) -> Document:
@@ -1,22 +1,20 @@
1
- from typing import Any, List, Literal, Optional, TypeAlias, Union
1
+ from typing import Any, List, Literal, Optional, 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
6
5
 
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
- # ]
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
+ ]
20
18
 
21
19
 
22
20
  class ActionStates(Enum):
@@ -79,7 +77,6 @@ class WindowExecutePayload(BaseModel):
79
77
  "close_window",
80
78
  "activate_window",
81
79
  ]
82
-
83
80
  window_name: str
84
81
  timeout: int = Field(default=10)
85
82
 
@@ -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: str | bytes) -> bytes:
48
+ def convert_to_bytes(cls, v) -> 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 = "left_click"
78
+ action_type: Literal["left_click"] = "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.anchors}').do()"
103
+ return f"LeftClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').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 = "right_click"
121
+ action_type: Literal["right_click"] = "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.anchors}').do()"
146
+ return f"RightClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').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 = "middle_click"
164
+ action_type: Literal["middle_click"] = "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.anchors}').do()"
189
+ return f"MiddleClickAction(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').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 = "double_click"
207
+ action_type: Literal["double_click"] = "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.anchors}').do()"
232
+ return f"DoubleClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').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 = "scroll"
252
+ action_type: Literal["scroll"] = "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.anchors}', keys='{self.keys}').do()"
336
+ return f"SendKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', 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 = "press_keys"
356
+ action_type: ActionTypes = "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.anchors}', keys='{self.keys}').do()"
369
+ return f"PressKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', 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 = "wait_for"
388
+ action_type: Literal["wait_for"] = "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 = "open_app"
453
+ action_type: Literal["open_app"] = "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 = "force_close_app"
482
+ action_type: Literal["force_close_app"] = "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 = "save_files"
512
+ action_type: ActionTypes = "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 = "get_file"
583
+ action_type: Literal["get_file"] = "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 = "maximize_window"
608
+ action_type: Literal["maximize_window"] = "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 = "minimize_window"
633
+ action_type: Literal["minimize_window"] = "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 = "close_window"
658
+ action_type: Literal["close_window"] = "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 = "activate_window"
683
+ action_type: Literal["activate_window"] = "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 = "get_text"
712
+ action_type: Literal["get_text"] = "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 = "paste_text"
755
+ action_type: Literal["paste_text"] = "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, TypeAlias, Union, List, Optional
1
+ from typing import Literal, Self, 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,7 +24,7 @@ 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: TypeAlias = Literal[
27
+ ActionTypes = Literal[
28
28
  "left_click",
29
29
  "right_click",
30
30
  "middle_click",
clerk/utils/logger.py CHANGED
@@ -6,7 +6,10 @@ import sys
6
6
  if sys.platform == "win32":
7
7
  base_path = os.path.join(os.getcwd(), "data", "artifacts")
8
8
  else:
9
- base_path = "/app/data/artifacts"
9
+ if os.getenv("__TEST"):
10
+ base_path = os.path.join(os.getcwd(), "data", "artifacts")
11
+ else:
12
+ base_path = "/app/data/artifacts"
10
13
 
11
14
  os.makedirs(base_path, exist_ok=True)
12
15
 
@@ -0,0 +1,254 @@
1
+ Metadata-Version: 2.4
2
+ Name: clerk-sdk
3
+ Version: 0.4.6
4
+ Summary: Library for interacting with Clerk
5
+ Home-page: https://github.com/F-ONE-Group/clerk_pypi
6
+ Author: F-ONE Group
7
+ Author-email: admin@f-one.group
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
15
+ Requires-Dist: backoff<3.0.0,>=2.0.0
16
+ Requires-Dist: requests<3.0.0,>=2.32.3
17
+ Provides-Extra: all
18
+ Requires-Dist: pydantic<3.0.0,>=2.0.0; extra == "all"
19
+ Requires-Dist: backoff<3.0.0,>=2.0.0; extra == "all"
20
+ Requires-Dist: requests<3.0.0,>=2.32.3; extra == "all"
21
+ Requires-Dist: networkx<4.0.0,>=3.5.0; extra == "all"
22
+ Requires-Dist: websockets>=15.0.1; extra == "all"
23
+ Provides-Extra: gui-automation
24
+ Requires-Dist: networkx<4.0.0,>=3.5.0; extra == "gui-automation"
25
+ Requires-Dist: websockets>=15.0.1; extra == "gui-automation"
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: license-file
33
+ Dynamic: provides-extra
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
37
+
38
+ # Clerk Python SDK
39
+
40
+ A production-ready Python client for the Clerk API. The SDK wraps Clerk's REST endpoints, rich document models, automation helpers, and structured task decorators so that your applications can create, update, and process Clerk documents with minimal boilerplate.
41
+
42
+ ## Table of Contents
43
+ - [Overview](#overview)
44
+ - [Key Features](#key-features)
45
+ - [Requirements](#requirements)
46
+ - [Installation](#installation)
47
+ - [Configuration](#configuration)
48
+ - [Quick Start](#quick-start)
49
+ - [Instantiate a Client](#instantiate-a-client)
50
+ - [Fetch Documents](#fetch-documents)
51
+ - [Upload a Document](#upload-a-document)
52
+ - [Update Structured Data](#update-structured-data)
53
+ - [Work with Files](#work-with-files)
54
+ - [Automation Utilities](#automation-utilities)
55
+ - [Task Decorator](#task-decorator)
56
+ - [GUI Automation Toolkit](#gui-automation-toolkit)
57
+ - [Error Handling](#error-handling)
58
+ - [Development Workflow](#development-workflow)
59
+ - [Contributing](#contributing)
60
+ - [License](#license)
61
+
62
+ ## Overview
63
+ The Clerk SDK centers around the `Clerk` client (`clerk.client.Clerk`), which extends a resilient `BaseClerk` transport layer with automatic retries and typed responses. Models under `clerk.models` provide Pydantic-powered validation for documents, files, and API payloads, ensuring type safety across network boundaries. Additional modules cover automated task execution via the `clerk.decorator` package and UI workflows under `clerk.gui_automation`.
64
+
65
+ ## Key Features
66
+ - **Document lifecycle management** – Create, fetch, list, and update Clerk documents with first-class models.
67
+ - **File handling** – Upload binary files or parsed base64 payloads and attach them to documents.
68
+ - **Robust networking** – Automatic retries for transient HTTP issues, configurable base URLs, and bearer authentication out of the box.
69
+ - **Structured task execution** – Decorators for running Clerk tasks locally or inside worker environments with consistent pickle-based I/O.
70
+ - **GUI automations** – Utilities for orchestrating low-level UI actions, state machines, and operator interactions when human-in-the-loop steps are required.
71
+
72
+ ## Requirements
73
+ - Python 3.10+
74
+ - Dependencies listed in [`requirements.txt`](requirements.txt), including `pydantic` and `backoff`.
75
+
76
+ ## Installation
77
+ Install the SDK from PyPI:
78
+
79
+ ```bash
80
+ pip install clerk-sdk
81
+ ```
82
+
83
+ For local development inside this repository, install the dependencies in editable mode:
84
+
85
+ ```bash
86
+ pip install -e .[dev]
87
+ ```
88
+
89
+ ## Configuration
90
+ The client reads configuration from keyword arguments or environment variables.
91
+
92
+ | Setting | Environment Variable | Description |
93
+ | --- | --- | --- |
94
+ | API key | `CLERK_API_KEY` | Required secret used for bearer authentication. |
95
+ | Base URL | `CLERK_BASE_URL` | Optional override of the default API host (`https://api.clerk-app.com`). |
96
+
97
+ ```bash
98
+ export CLERK_API_KEY="sk_live_123"
99
+ export CLERK_BASE_URL="https://staging.clerk-app.com" # optional
100
+ ```
101
+
102
+ You can also pass the API key directly when instantiating `Clerk`:
103
+
104
+ ```python
105
+ from clerk import Clerk
106
+
107
+ client = Clerk(api_key="sk_live_123")
108
+ ```
109
+
110
+ ## Quick Start
111
+ The following snippets demonstrate the core document operations supported by the SDK.
112
+
113
+ ### Instantiate a Client
114
+ ```python
115
+ from clerk import Clerk
116
+
117
+ client = Clerk(api_key="sk_live_123")
118
+ ```
119
+
120
+ ### Fetch Documents
121
+ Retrieve a single document by its identifier or list documents with query filters.
122
+
123
+ ```python
124
+ from clerk.models.document import GetDocumentsRequest
125
+
126
+ # Single document
127
+ invoice = client.get_document(document_id="doc_123")
128
+ print(invoice.title, invoice.status)
129
+
130
+ # Query multiple documents
131
+ request = GetDocumentsRequest(project_id="proj_456", limit=25)
132
+ documents = client.get_documents(request)
133
+ for doc in documents:
134
+ print(doc.id, doc.status)
135
+ ```
136
+
137
+ ### Upload a Document
138
+ Use `UploadDocumentRequest` to send metadata and file attachments. Files can be supplied as paths or `ParsedFile` instances.
139
+
140
+ ```python
141
+ from clerk.models.document import UploadDocumentRequest
142
+
143
+ upload_request = UploadDocumentRequest(
144
+ project_id="proj_456",
145
+ message_subject="Invoice 2024-01",
146
+ files=["/path/to/invoice.pdf"],
147
+ input_structured_data={"customer_id": "cust_789"},
148
+ )
149
+
150
+ created = client.upload_document(upload_request)
151
+ print(f"Created document: {created.id}")
152
+ ```
153
+
154
+ ### Update Structured Data
155
+ Patch a document's structured payload without re-uploading files.
156
+
157
+ ```python
158
+ updated = client.update_document_structured_data(
159
+ document_id="doc_123",
160
+ updated_structured_data={"status": "processed", "processed_by": "automation"},
161
+ )
162
+ print(updated.structured_data)
163
+ ```
164
+
165
+ ### Work with Files
166
+ Retrieve parsed file metadata or attach additional files to existing documents.
167
+
168
+ ```python
169
+ from clerk.models.file import UploadFile
170
+
171
+ # List associated files
172
+ files = client.get_files_document(document_id="doc_123")
173
+ for file in files:
174
+ print(file.name, file.mimetype)
175
+
176
+ # Append output files
177
+ client.add_files_to_document(
178
+ document_id="doc_123",
179
+ type="output",
180
+ files=[
181
+ UploadFile(name="summary.txt", mimetype="text/plain", content=b"Processed")
182
+ ],
183
+ )
184
+ ```
185
+
186
+ ## Custom Code Utilities
187
+ ### Task Decorator
188
+ The `@clerk_code` decorator standardizes how Clerk tasks load inputs and persist outputs when executed by the Clerk workflow. It automatically reads a pickled `ClerkCodePayload` from `/app/data/input/input.pkl`, executes your function, and writes the result (or an `ApplicationException`) to `/app/data/output/output.pkl`.
189
+
190
+ ```python
191
+ from clerk.decorator import clerk_code
192
+ from clerk.decorator.models import ClerkCodePayload, Document
193
+
194
+ @clerk_code()
195
+ def handle_document(payload: ClerkCodePayload) -> ClerkCodePayload:
196
+ document: Document = payload.document
197
+ payload.structured_data = payload.structured_data or {}
198
+ payload.structured_data["status"] = f"Processed {document.id}"
199
+ return payload
200
+
201
+ if __name__ == "__main__":
202
+ handle_document() # Auto-loads from pickle files when payload is omitted
203
+ ```
204
+
205
+ For unit testing, you can bypass the pickle integration by passing an explicit payload instance:
206
+
207
+ ```python
208
+ from datetime import datetime
209
+ from clerk.decorator.models import ClerkCodePayload, Document
210
+ from clerk.models.document_statuses import DocumentStatuses
211
+
212
+ sample_payload = ClerkCodePayload(
213
+ document=Document(
214
+ id="doc_123",
215
+ project_id="proj_456",
216
+ title="Sample",
217
+ upload_date=datetime.utcnow(),
218
+ status=DocumentStatuses.draft,
219
+ created_at=datetime.utcnow(),
220
+ updated_at=datetime.utcnow(),
221
+ ),
222
+ structured_data={},
223
+ )
224
+ result = handle_document(sample_payload)
225
+ assert "Processed" in result.structured_data["status"]
226
+ ```
227
+
228
+ > **Note:** Refer to `tests/test_task_decorator.py` for additional usage examples covering error propagation and pickle round-trips.
229
+
230
+ ### GUI Automation Toolkit
231
+ The `clerk.gui_automation` package contains models, actions, and state machines for orchestrating UI interactions. Highlights include:
232
+
233
+ - `BaseAction` and concrete actions for cursor movement, clicks, and keyboard input.
234
+ - `ActionModel` builders that translate payloads into executable UI sequences.
235
+ - State machine primitives (`ui_state_machine`) to coordinate multi-step automations.
236
+ - Helpers for safely reading files, validating anchors, and converting payload flags.
237
+
238
+ These utilities are designed to be composed with your own automation runners or integrated into Clerk tasks. Review the tests in `tests/test_gui_automation.py` for patterns on stubbing operator clients and verifying payload transformations.
239
+
240
+ ## Error Handling
241
+ All network helpers raise `requests` exceptions for HTTP errors. When using the task decorator, runtime failures are wrapped in `ApplicationException` objects that capture the exception type, message, and traceback for easier debugging. Deserialize the returned payload or inspect the pickle output to handle errors gracefully.
242
+
243
+ ## Development Workflow
244
+ 1. **Clone the repository** and install dependencies with `pip install -e .[dev]`.
245
+ 2. **Run the test suite** using `pytest`. The CI workflow executes these tests before packaging releases.
246
+ 3. **Add type-safe models** or extend the client in `clerk/client.py` and `clerk/models` as needed.
247
+ 4. **Contribute automations** under `clerk/gui_automation` by following the established action/state machine patterns.
248
+ 5. **Commit and open a pull request** once tests pass and documentation is updated.
249
+
250
+ ## Contributing
251
+ Contributions are welcome! Please open an issue to discuss substantial changes, follow the existing code style (Pydantic models, typed functions, and pytest fixtures), and ensure the test suite passes before submitting a pull request.
252
+
253
+ ## License
254
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
@@ -1,6 +1,6 @@
1
- clerk/__init__.py,sha256=LWpbImG7352mUJYC1tRm_zsn5rnt4sFl5ldoq5f0dlo,26
1
+ clerk/__init__.py,sha256=ubAUuMPlxiWOVmwL7aMXdRDmiXjmyqibfKLX5ZcLZR4,50
2
2
  clerk/base.py,sha256=S1RKc2pBw2FPlVjefJzsNtyTDPB0UG46C2K_QVV1opA,4008
3
- clerk/client.py,sha256=Ma6nCICRBjgdcntPaEJv8baMBr7zftKeLcEzfD634eA,2368
3
+ clerk/client.py,sha256=RdOvC23WK9ZtIXDOYaoSFk9debh3UTmstBXjAswAH6E,4981
4
4
  clerk/decorator/__init__.py,sha256=yGGcS17VsZ7cZ-hVGCm3I3vGDJMiJIAqmDGzriIi0DI,65
5
5
  clerk/decorator/models.py,sha256=PiAugPvX1c6BQBWtfhyh5k9Uau2OJNDIx7T1TLz7ZFY,409
6
6
  clerk/decorator/task_decorator.py,sha256=H8caRvNvvl-IRwyREP66gBGVM-SpQJ1W7oAFImO-6Jw,3769
@@ -15,7 +15,7 @@ clerk/gui_automation/action_model/utils.py,sha256=xzFxgN-bTK6HKGS7J-esQZ-ePj_yG7
15
15
  clerk/gui_automation/client_actor/__init__.py,sha256=SVuL6-oo1Xc0oJkjMKrO6mJwpPGjrCLKhDV6r2Abtf8,66
16
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=B6tYQB0ngY3oM6pWIoJLTi_H9U-hTgQqal7b7rSWkd8,6433
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
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
@@ -24,8 +24,8 @@ clerk/gui_automation/exceptions/websocket.py,sha256=-MdwSwlf1hbnu55aDgk3L1znkTZ6
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=qFustr0Wr81Bq6tS9sPqqFMC5zwG5-XmjKy1kFwjSKc,26914
28
- clerk/gui_automation/ui_actions/base.py,sha256=ymYWZnleywyI2DeaAb_HuDxgoSgfgw0zjXyQGp4Nvqo,7351
27
+ clerk/gui_automation/ui_actions/actions.py,sha256=hhxl5VMDNSXdqm2L0tZqs6IhJHVXtlSVSdwsiz2BbDI,27449
28
+ clerk/gui_automation/ui_actions/base.py,sha256=oaUI3vIOoDwP_HdLu2GIG46-aMv0_Zv-PljMgSFeNmk,7329
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
31
  clerk/gui_automation/ui_state_inspector/gui_vision.py,sha256=Pk5nuFZnp_zNbqSOtndSmgqb6PLeADJfnC-eRIJMDZk,7736
@@ -44,10 +44,10 @@ clerk/models/remote_device.py,sha256=2jijS-9WWq7y6xIbAaDaPnzZo3-tjp2-dCQvNKP8YSU
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
- clerk/utils/logger.py,sha256=vHbp-lUK3W3c5fhyPsp05p9LGyDHj95v8XM_XQ_FlLc,3691
47
+ clerk/utils/logger.py,sha256=NrMIlJfVmRjjRw_N_Jngkl0qqv7btXUbg5wxcRmFEH4,3800
48
48
  clerk/utils/save_artifact.py,sha256=94aYkYNVGcSUaSWZmdjiY6Oc-3yCKb2XWCZ56IAXQqk,1158
49
- clerk_sdk-0.4.4.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
50
- clerk_sdk-0.4.4.dist-info/METADATA,sha256=Gehi5QfBm5ldOpjQPY7WWNCVd1KFHqk3W_W8r_aTLzA,3508
51
- clerk_sdk-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
- clerk_sdk-0.4.4.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
53
- clerk_sdk-0.4.4.dist-info/RECORD,,
49
+ clerk_sdk-0.4.6.dist-info/licenses/LICENSE,sha256=GTVQl3vH6ht70wJXKC0yMT8CmXKHxv_YyO_utAgm7EA,1065
50
+ clerk_sdk-0.4.6.dist-info/METADATA,sha256=gn2Ih5DuBL3CSIvPbu5HPEmw5DunhRv8GcpqGkUEqDU,9936
51
+ clerk_sdk-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ clerk_sdk-0.4.6.dist-info/top_level.txt,sha256=99eQiU6d05_-f41tmSFanfI_SIJeAdh7u9m3LNSfcv4,6
53
+ clerk_sdk-0.4.6.dist-info/RECORD,,
@@ -1,128 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: clerk-sdk
3
- Version: 0.4.4
4
- Summary: Library for interacting with Clerk
5
- Home-page: https://github.com/F-ONE-Group/clerk_pypi
6
- Author: F-ONE Group
7
- Author-email: admin@f-one.group
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.10
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: pydantic<3.0.0,>=2.0.0
15
- Requires-Dist: backoff<3.0.0,>=2.0.0
16
- Requires-Dist: requests<3.0.0,>=2.32.3
17
- Provides-Extra: all
18
- Requires-Dist: pydantic<3.0.0,>=2.0.0; extra == "all"
19
- Requires-Dist: backoff<3.0.0,>=2.0.0; extra == "all"
20
- Requires-Dist: requests<3.0.0,>=2.32.3; extra == "all"
21
- Requires-Dist: networkx<4.0.0,>=3.5.0; extra == "all"
22
- Requires-Dist: websockets>=15.0.1; extra == "all"
23
- Provides-Extra: gui-automation
24
- Requires-Dist: networkx<4.0.0,>=3.5.0; extra == "gui-automation"
25
- Requires-Dist: websockets>=15.0.1; extra == "gui-automation"
26
- Dynamic: author
27
- Dynamic: author-email
28
- Dynamic: classifier
29
- Dynamic: description
30
- Dynamic: description-content-type
31
- Dynamic: home-page
32
- Dynamic: license-file
33
- Dynamic: provides-extra
34
- Dynamic: requires-dist
35
- Dynamic: requires-python
36
- Dynamic: summary
37
-
38
- # CLERK
39
-
40
- `clerk-sdk` is a Python library designed to simplify interactions with the Clerk API. It provides a robust and user-friendly interface for managing documents, handling API requests, and integrating structured data models into your workflows. `clerk-sdk` is ideal for developers looking to streamline their integration with Clerk.
41
-
42
- ## Features
43
-
44
- - **Document Management**: Retrieve and manage documents and their associated files.
45
- - **API Request Handling**: Simplified GET and POST requests with automatic retries and error handling.
46
- - **Data Models**: Predefined Pydantic models for structured data validation and serialization.
47
- - **Task Flow Integration**: Decorator for creating and managing task flows.
48
- - **Extensibility**: Easily extend and customize the library to fit your specific use case.
49
-
50
- ## Installation
51
-
52
- Install the library using pip:
53
-
54
- ```bash
55
- pip install clerk-sdk
56
- ```
57
-
58
- ## Usage
59
-
60
- ### Initialize the Client
61
-
62
- ```python
63
- from clerk import Clerk
64
-
65
- clerk_client = Clerk(api_key="your_api_key")
66
- ```
67
-
68
- ### Retrieve a Document
69
-
70
- ```python
71
- document = clerk_client.get_document(document_id="12345")
72
- print(document.title)
73
- ```
74
-
75
- ### Retrieve Files Associated with a Document
76
-
77
- ```python
78
- files = clerk_client.get_files_document(document_id="12345")
79
- for file in files:
80
- print(file.name)
81
- ```
82
-
83
- ### Use the Task Decorator
84
-
85
- #### PROD
86
-
87
- ```python
88
- from clerk.decorator import clerk_code
89
- from clerk.decorator.models import ClerkCodePayload
90
-
91
- @clerk_code()
92
- def main(payload: ClerkCodePayload) -> ClerkCodePayload:
93
- payload.structured_data["status"] = "ok"
94
- return payload
95
-
96
- main()
97
- ```
98
-
99
- #### TEST
100
-
101
- ```python
102
- from clerk.decorator.models import ClerkCodePayload, Document
103
-
104
- def test_main():
105
- test_payload = ClerkCodePayload(
106
- document=Document(id="doc-123", message_subject="Hello"),
107
- structured_data={}
108
- )
109
-
110
- result = main(test_payload) # ✅ Just pass it!
111
- assert result.structured_data["status"] == "ok"
112
- ```
113
-
114
-
115
- ## Requirements
116
-
117
- - Python 3.10+
118
- - Dependencies listed in `requirements.txt`:
119
- - `pydantic>2.0.0`
120
- - `backoff>2.0.0`
121
-
122
- ## License
123
-
124
- This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
125
-
126
- ## Contributing
127
-
128
- Contributions are welcome! Please submit a pull request or open an issue to discuss your ideas.