clerk-sdk 0.4.5__tar.gz → 0.4.6__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.
- clerk_sdk-0.4.6/PKG-INFO +254 -0
- clerk_sdk-0.4.6/README.md +217 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/__init__.py +3 -0
- clerk_sdk-0.4.6/clerk/client.py +138 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/utils/logger.py +4 -1
- clerk_sdk-0.4.6/clerk_sdk.egg-info/PKG-INFO +254 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk_sdk.egg-info/SOURCES.txt +9 -1
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/setup.py +2 -2
- clerk_sdk-0.4.6/tests/test_base.py +154 -0
- clerk_sdk-0.4.6/tests/test_client.py +164 -0
- clerk_sdk-0.4.6/tests/test_document_models.py +79 -0
- clerk_sdk-0.4.6/tests/test_exceptions.py +14 -0
- clerk_sdk-0.4.6/tests/test_file_models.py +26 -0
- clerk_sdk-0.4.6/tests/test_gui_automation.py +266 -0
- clerk_sdk-0.4.6/tests/test_task_decorator.py +76 -0
- clerk_sdk-0.4.6/tests/test_utils.py +29 -0
- clerk_sdk-0.4.5/PKG-INFO +0 -128
- clerk_sdk-0.4.5/README.md +0 -91
- clerk_sdk-0.4.5/clerk/client.py +0 -66
- clerk_sdk-0.4.5/clerk_sdk.egg-info/PKG-INFO +0 -128
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/LICENSE +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/MANIFEST.in +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/base.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/decorator/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/decorator/models.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/decorator/task_decorator.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/exceptions/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/exceptions/exceptions.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/exceptions/remote_device.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/action_model/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/action_model/model.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/action_model/utils.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/client.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/client_actor/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/client_actor/client_actor.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/client_actor/exception.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/client_actor/model.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/decorators/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/decorators/gui_automation.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/exceptions/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/exceptions/agent_manager.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/exceptions/modality/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/exceptions/modality/exc.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/exceptions/websocket.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/requirements.txt +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_actions/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_actions/actions.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_actions/base.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_actions/support.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_inspector/gui_vision.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_inspector/models.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/ai_recovery.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/decorators.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/exceptions.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/models.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/gui_automation/ui_state_machine/state_machine.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/document.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/document_statuses.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/file.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/remote_device.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/response_model.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/models/ui_operator.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/utils/__init__.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk/utils/save_artifact.py +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk_sdk.egg-info/dependency_links.txt +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk_sdk.egg-info/requires.txt +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/clerk_sdk.egg-info/top_level.txt +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/pyproject.toml +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/requirements.txt +0 -0
- {clerk_sdk-0.4.5 → clerk_sdk-0.4.6}/setup.cfg +0 -0
clerk_sdk-0.4.6/PKG-INFO
ADDED
|
@@ -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.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Clerk Python SDK
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Key Features](#key-features)
|
|
8
|
+
- [Requirements](#requirements)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Configuration](#configuration)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Instantiate a Client](#instantiate-a-client)
|
|
13
|
+
- [Fetch Documents](#fetch-documents)
|
|
14
|
+
- [Upload a Document](#upload-a-document)
|
|
15
|
+
- [Update Structured Data](#update-structured-data)
|
|
16
|
+
- [Work with Files](#work-with-files)
|
|
17
|
+
- [Automation Utilities](#automation-utilities)
|
|
18
|
+
- [Task Decorator](#task-decorator)
|
|
19
|
+
- [GUI Automation Toolkit](#gui-automation-toolkit)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [Development Workflow](#development-workflow)
|
|
22
|
+
- [Contributing](#contributing)
|
|
23
|
+
- [License](#license)
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
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`.
|
|
27
|
+
|
|
28
|
+
## Key Features
|
|
29
|
+
- **Document lifecycle management** – Create, fetch, list, and update Clerk documents with first-class models.
|
|
30
|
+
- **File handling** – Upload binary files or parsed base64 payloads and attach them to documents.
|
|
31
|
+
- **Robust networking** – Automatic retries for transient HTTP issues, configurable base URLs, and bearer authentication out of the box.
|
|
32
|
+
- **Structured task execution** – Decorators for running Clerk tasks locally or inside worker environments with consistent pickle-based I/O.
|
|
33
|
+
- **GUI automations** – Utilities for orchestrating low-level UI actions, state machines, and operator interactions when human-in-the-loop steps are required.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
- Python 3.10+
|
|
37
|
+
- Dependencies listed in [`requirements.txt`](requirements.txt), including `pydantic` and `backoff`.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
Install the SDK from PyPI:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install clerk-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For local development inside this repository, install the dependencies in editable mode:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install -e .[dev]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
The client reads configuration from keyword arguments or environment variables.
|
|
54
|
+
|
|
55
|
+
| Setting | Environment Variable | Description |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| API key | `CLERK_API_KEY` | Required secret used for bearer authentication. |
|
|
58
|
+
| Base URL | `CLERK_BASE_URL` | Optional override of the default API host (`https://api.clerk-app.com`). |
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export CLERK_API_KEY="sk_live_123"
|
|
62
|
+
export CLERK_BASE_URL="https://staging.clerk-app.com" # optional
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
You can also pass the API key directly when instantiating `Clerk`:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from clerk import Clerk
|
|
69
|
+
|
|
70
|
+
client = Clerk(api_key="sk_live_123")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
The following snippets demonstrate the core document operations supported by the SDK.
|
|
75
|
+
|
|
76
|
+
### Instantiate a Client
|
|
77
|
+
```python
|
|
78
|
+
from clerk import Clerk
|
|
79
|
+
|
|
80
|
+
client = Clerk(api_key="sk_live_123")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Fetch Documents
|
|
84
|
+
Retrieve a single document by its identifier or list documents with query filters.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from clerk.models.document import GetDocumentsRequest
|
|
88
|
+
|
|
89
|
+
# Single document
|
|
90
|
+
invoice = client.get_document(document_id="doc_123")
|
|
91
|
+
print(invoice.title, invoice.status)
|
|
92
|
+
|
|
93
|
+
# Query multiple documents
|
|
94
|
+
request = GetDocumentsRequest(project_id="proj_456", limit=25)
|
|
95
|
+
documents = client.get_documents(request)
|
|
96
|
+
for doc in documents:
|
|
97
|
+
print(doc.id, doc.status)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Upload a Document
|
|
101
|
+
Use `UploadDocumentRequest` to send metadata and file attachments. Files can be supplied as paths or `ParsedFile` instances.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from clerk.models.document import UploadDocumentRequest
|
|
105
|
+
|
|
106
|
+
upload_request = UploadDocumentRequest(
|
|
107
|
+
project_id="proj_456",
|
|
108
|
+
message_subject="Invoice 2024-01",
|
|
109
|
+
files=["/path/to/invoice.pdf"],
|
|
110
|
+
input_structured_data={"customer_id": "cust_789"},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
created = client.upload_document(upload_request)
|
|
114
|
+
print(f"Created document: {created.id}")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Update Structured Data
|
|
118
|
+
Patch a document's structured payload without re-uploading files.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
updated = client.update_document_structured_data(
|
|
122
|
+
document_id="doc_123",
|
|
123
|
+
updated_structured_data={"status": "processed", "processed_by": "automation"},
|
|
124
|
+
)
|
|
125
|
+
print(updated.structured_data)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Work with Files
|
|
129
|
+
Retrieve parsed file metadata or attach additional files to existing documents.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from clerk.models.file import UploadFile
|
|
133
|
+
|
|
134
|
+
# List associated files
|
|
135
|
+
files = client.get_files_document(document_id="doc_123")
|
|
136
|
+
for file in files:
|
|
137
|
+
print(file.name, file.mimetype)
|
|
138
|
+
|
|
139
|
+
# Append output files
|
|
140
|
+
client.add_files_to_document(
|
|
141
|
+
document_id="doc_123",
|
|
142
|
+
type="output",
|
|
143
|
+
files=[
|
|
144
|
+
UploadFile(name="summary.txt", mimetype="text/plain", content=b"Processed")
|
|
145
|
+
],
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Custom Code Utilities
|
|
150
|
+
### Task Decorator
|
|
151
|
+
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`.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from clerk.decorator import clerk_code
|
|
155
|
+
from clerk.decorator.models import ClerkCodePayload, Document
|
|
156
|
+
|
|
157
|
+
@clerk_code()
|
|
158
|
+
def handle_document(payload: ClerkCodePayload) -> ClerkCodePayload:
|
|
159
|
+
document: Document = payload.document
|
|
160
|
+
payload.structured_data = payload.structured_data or {}
|
|
161
|
+
payload.structured_data["status"] = f"Processed {document.id}"
|
|
162
|
+
return payload
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
handle_document() # Auto-loads from pickle files when payload is omitted
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For unit testing, you can bypass the pickle integration by passing an explicit payload instance:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from datetime import datetime
|
|
172
|
+
from clerk.decorator.models import ClerkCodePayload, Document
|
|
173
|
+
from clerk.models.document_statuses import DocumentStatuses
|
|
174
|
+
|
|
175
|
+
sample_payload = ClerkCodePayload(
|
|
176
|
+
document=Document(
|
|
177
|
+
id="doc_123",
|
|
178
|
+
project_id="proj_456",
|
|
179
|
+
title="Sample",
|
|
180
|
+
upload_date=datetime.utcnow(),
|
|
181
|
+
status=DocumentStatuses.draft,
|
|
182
|
+
created_at=datetime.utcnow(),
|
|
183
|
+
updated_at=datetime.utcnow(),
|
|
184
|
+
),
|
|
185
|
+
structured_data={},
|
|
186
|
+
)
|
|
187
|
+
result = handle_document(sample_payload)
|
|
188
|
+
assert "Processed" in result.structured_data["status"]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> **Note:** Refer to `tests/test_task_decorator.py` for additional usage examples covering error propagation and pickle round-trips.
|
|
192
|
+
|
|
193
|
+
### GUI Automation Toolkit
|
|
194
|
+
The `clerk.gui_automation` package contains models, actions, and state machines for orchestrating UI interactions. Highlights include:
|
|
195
|
+
|
|
196
|
+
- `BaseAction` and concrete actions for cursor movement, clicks, and keyboard input.
|
|
197
|
+
- `ActionModel` builders that translate payloads into executable UI sequences.
|
|
198
|
+
- State machine primitives (`ui_state_machine`) to coordinate multi-step automations.
|
|
199
|
+
- Helpers for safely reading files, validating anchors, and converting payload flags.
|
|
200
|
+
|
|
201
|
+
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.
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
## Development Workflow
|
|
207
|
+
1. **Clone the repository** and install dependencies with `pip install -e .[dev]`.
|
|
208
|
+
2. **Run the test suite** using `pytest`. The CI workflow executes these tests before packaging releases.
|
|
209
|
+
3. **Add type-safe models** or extend the client in `clerk/client.py` and `clerk/models` as needed.
|
|
210
|
+
4. **Contribute automations** under `clerk/gui_automation` by following the established action/state machine patterns.
|
|
211
|
+
5. **Commit and open a pull request** once tests pass and documentation is updated.
|
|
212
|
+
|
|
213
|
+
## Contributing
|
|
214
|
+
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.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Literal
|
|
2
|
+
|
|
3
|
+
from clerk.base import BaseClerk
|
|
4
|
+
from clerk.models.document import Document, GetDocumentsRequest, UploadDocumentRequest
|
|
5
|
+
from .models.file import ParsedFile, UploadFile
|
|
6
|
+
|
|
7
|
+
|
|
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 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
|
+
|
|
89
|
+
def update_document_structured_data(
|
|
90
|
+
self, document_id: str, updated_structured_data: Dict[str, Any]
|
|
91
|
+
) -> Document:
|
|
92
|
+
endpoint = f"/document/{document_id}"
|
|
93
|
+
payload = dict(structured_data=updated_structured_data)
|
|
94
|
+
res = self.put_request(endpoint, json=payload)
|
|
95
|
+
|
|
96
|
+
return Document(**res.data[0])
|
|
97
|
+
|
|
98
|
+
def get_document(self, document_id: str) -> Document:
|
|
99
|
+
endpoint = f"/document/{document_id}"
|
|
100
|
+
res = self.get_request(endpoint=endpoint)
|
|
101
|
+
return Document(**res.data[0])
|
|
102
|
+
|
|
103
|
+
def get_documents(self, request: GetDocumentsRequest) -> List[Document]:
|
|
104
|
+
if not any(
|
|
105
|
+
[
|
|
106
|
+
request.organization_id,
|
|
107
|
+
request.project_id,
|
|
108
|
+
request.start_date,
|
|
109
|
+
request.end_date,
|
|
110
|
+
request.query,
|
|
111
|
+
request.include_statuses,
|
|
112
|
+
]
|
|
113
|
+
):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"At least one query parameter (organization_id, project_id, start_date, end_date, query, or include_statuses) must be provided."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
endpoint = f"/documents"
|
|
119
|
+
params = request.model_dump(mode="json")
|
|
120
|
+
res = self.get_request(endpoint, params=params)
|
|
121
|
+
|
|
122
|
+
return [Document(**d) for d in res.data]
|
|
123
|
+
|
|
124
|
+
def get_files_document(self, document_id: str) -> List[ParsedFile]:
|
|
125
|
+
endpoint = f"/document/{document_id}/files"
|
|
126
|
+
res = self.get_request(endpoint=endpoint)
|
|
127
|
+
return [ParsedFile(**d) for d in res.data]
|
|
128
|
+
|
|
129
|
+
def add_files_to_document(
|
|
130
|
+
self,
|
|
131
|
+
document_id: str,
|
|
132
|
+
type: Literal["input", "output"],
|
|
133
|
+
files: List[UploadFile],
|
|
134
|
+
):
|
|
135
|
+
endpoint = f"/document/{document_id}/files/upload"
|
|
136
|
+
params = {"type": type}
|
|
137
|
+
files_data = [f.to_multipart_format() for f in files]
|
|
138
|
+
self.post_request(endpoint, params=params, files=files_data)
|
|
@@ -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
|
-
|
|
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
|
|