clerk-sdk 0.5.1__tar.gz → 0.5.3__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.5.1 → clerk_sdk-0.5.3}/PKG-INFO +3 -2
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/README.md +1 -1
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/__init__.py +1 -1
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/cli.py +57 -4
- clerk_sdk-0.5.3/clerk/development/code_runner.py +384 -0
- clerk_sdk-0.5.3/clerk/development/gui/graph_checker.py +216 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/gui/test_session.py +3 -3
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/init_project.py +22 -0
- clerk_sdk-0.5.3/clerk/development/templates/launch.json.template +20 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/main_gui.py.template +3 -3
- clerk_sdk-0.5.3/clerk/development/templates/tasks.json.template +25 -0
- clerk_sdk-0.5.3/clerk/development/templates/test_payload.py.template +32 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/document.py +1 -2
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/pyproject.toml +2 -1
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/requirements.txt +1 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/setup.py +1 -1
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_client.py +7 -3
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_document_models.py +11 -5
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/uv.lock +27 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/.github/workflows/ci.yaml +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/.github/workflows/pypi_publish.yml +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/.gitignore +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/LICENSE +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/MANIFEST.in +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/base.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/client.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/decorator/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/decorator/models.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/decorator/task_decorator.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/schema/fetch_schema.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/exceptions.py.template +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/main_basic.py.template +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/rollbacks.py.template +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/states.py.template +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/development/templates/transitions.py.template +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/exceptions/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/exceptions/exceptions.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/exceptions/remote_device.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/action_model/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/action_model/model.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/action_model/utils.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/client.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/client_actor/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/client_actor/client_actor.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/client_actor/exception.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/client_actor/model.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/decorators/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/decorators/gui_automation.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/exceptions/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/exceptions/agent_manager.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/exceptions/modality/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/exceptions/modality/exc.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/exceptions/websocket.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/requirements.txt +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_actions/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_actions/actions.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_actions/base.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_actions/support.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_inspector/gui_vision.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_inspector/models.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/Readme.md +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/ai_recovery.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/decorators.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/exceptions.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/gui_automation/ui_state_machine/state_machine.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/document_statuses.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/file.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/remote_device.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/response_model.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/models/ui_operator.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/utils/__init__.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/utils/logger.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/clerk/utils/save_artifact.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/conftest.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_base.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_exceptions.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_file_models.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_gui_automation.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_task_decorator.py +0 -0
- {clerk_sdk-0.5.1 → clerk_sdk-0.5.3}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clerk-sdk
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Library for interacting with Clerk
|
|
5
5
|
Project-URL: Homepage, https://github.com/F-ONE-Group/clerk_pypi
|
|
6
6
|
Author-email: F-One <contact@f-one.group>
|
|
@@ -10,6 +10,7 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
12
|
Requires-Dist: backoff<3.0.0,>=2.0.0
|
|
13
|
+
Requires-Dist: debugpy<2.0.0,>=1.8.0
|
|
13
14
|
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
14
15
|
Requires-Dist: python-dotenv>=1.0.0
|
|
15
16
|
Requires-Dist: requests<3.0.0,>=2.32.3
|
|
@@ -128,7 +129,7 @@ Use `UploadDocumentRequest` to send metadata and file attachments. Files can be
|
|
|
128
129
|
from clerk.models.document import UploadDocumentRequest
|
|
129
130
|
|
|
130
131
|
upload_request = UploadDocumentRequest(
|
|
131
|
-
|
|
132
|
+
workflow_id="proj_456",
|
|
132
133
|
message_subject="Invoice 2024-01",
|
|
133
134
|
files=["/path/to/invoice.pdf"],
|
|
134
135
|
input_structured_data={"customer_id": "cust_789"},
|
|
@@ -104,7 +104,7 @@ Use `UploadDocumentRequest` to send metadata and file attachments. Files can be
|
|
|
104
104
|
from clerk.models.document import UploadDocumentRequest
|
|
105
105
|
|
|
106
106
|
upload_request = UploadDocumentRequest(
|
|
107
|
-
|
|
107
|
+
workflow_id="proj_456",
|
|
108
108
|
message_subject="Invoice 2024-01",
|
|
109
109
|
files=["/path/to/invoice.pdf"],
|
|
110
110
|
input_structured_data={"customer_id": "cust_789"},
|
|
@@ -53,26 +53,58 @@ def main():
|
|
|
53
53
|
help="GUI automation commands"
|
|
54
54
|
)
|
|
55
55
|
gui_subparsers = gui_parser.add_subparsers(dest="gui_command", help="GUI subcommands")
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
# GUI connect subcommand
|
|
58
58
|
gui_connect_parser = gui_subparsers.add_parser(
|
|
59
59
|
"connect",
|
|
60
60
|
help="Start interactive GUI automation test session"
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
# GUI graph check subcommand
|
|
64
|
+
gui_graph_parser = gui_subparsers.add_parser(
|
|
65
|
+
"graph", help="Graph analysis commands"
|
|
66
|
+
)
|
|
67
|
+
gui_graph_subparsers = gui_graph_parser.add_subparsers(
|
|
68
|
+
dest="graph_command", help="Graph subcommands"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
gui_graph_check_parser = gui_graph_subparsers.add_parser(
|
|
72
|
+
"check", help="Check and visualize state machine graph structure"
|
|
73
|
+
)
|
|
74
|
+
gui_graph_check_parser.add_argument(
|
|
75
|
+
"--module-path",
|
|
76
|
+
type=str,
|
|
77
|
+
required=False,
|
|
78
|
+
default=None,
|
|
79
|
+
help="Path to the Python file containing the state machine (defaults to src/main.py)",
|
|
80
|
+
)
|
|
81
|
+
|
|
63
82
|
# Schema command group
|
|
64
83
|
schema_parser = subparsers.add_parser(
|
|
65
84
|
"schema",
|
|
66
85
|
help="Schema management commands"
|
|
67
86
|
)
|
|
68
87
|
schema_subparsers = schema_parser.add_subparsers(dest="schema_command", help="Schema subcommands")
|
|
69
|
-
|
|
88
|
+
|
|
70
89
|
# Schema fetch subcommand
|
|
71
90
|
schema_fetch_parser = schema_subparsers.add_parser(
|
|
72
91
|
"fetch",
|
|
73
92
|
help="Fetch and generate Pydantic models from project schema"
|
|
74
93
|
)
|
|
75
94
|
|
|
95
|
+
# Code command group
|
|
96
|
+
code_parser = subparsers.add_parser(
|
|
97
|
+
"code", help="Custom code development and testing commands"
|
|
98
|
+
)
|
|
99
|
+
code_subparsers = code_parser.add_subparsers(
|
|
100
|
+
dest="code_command", help="Code subcommands"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Code run subcommand
|
|
104
|
+
code_run_parser = code_subparsers.add_parser(
|
|
105
|
+
"run", help="Run custom code with test payloads"
|
|
106
|
+
)
|
|
107
|
+
|
|
76
108
|
args = parser.parse_args()
|
|
77
109
|
|
|
78
110
|
# Show help if no command specified
|
|
@@ -90,16 +122,27 @@ def main():
|
|
|
90
122
|
if not hasattr(args, 'gui_command') or not args.gui_command:
|
|
91
123
|
gui_parser.print_help()
|
|
92
124
|
sys.exit(1)
|
|
93
|
-
|
|
125
|
+
|
|
94
126
|
if args.gui_command == "connect":
|
|
95
127
|
from clerk.development.gui.test_session import main as gui_main
|
|
96
128
|
gui_main()
|
|
97
129
|
|
|
130
|
+
elif args.gui_command == "graph":
|
|
131
|
+
if not hasattr(args, "graph_command") or not args.graph_command:
|
|
132
|
+
print("Error: graph command requires a subcommand")
|
|
133
|
+
print("Available subcommands: check")
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
if args.graph_command == "check":
|
|
137
|
+
from clerk.development.gui.graph_checker import check_graph
|
|
138
|
+
|
|
139
|
+
check_graph(args.module_path)
|
|
140
|
+
|
|
98
141
|
elif args.command == "schema":
|
|
99
142
|
if not hasattr(args, 'schema_command') or not args.schema_command:
|
|
100
143
|
schema_parser.print_help()
|
|
101
144
|
sys.exit(1)
|
|
102
|
-
|
|
145
|
+
|
|
103
146
|
if args.schema_command == "fetch":
|
|
104
147
|
from clerk.development.schema.fetch_schema import main_with_args
|
|
105
148
|
project_id = os.getenv("PROJECT_ID")
|
|
@@ -108,6 +151,16 @@ def main():
|
|
|
108
151
|
sys.exit(1)
|
|
109
152
|
main_with_args(project_id, project_root)
|
|
110
153
|
|
|
154
|
+
elif args.command == "code":
|
|
155
|
+
if not hasattr(args, "code_command") or not args.code_command:
|
|
156
|
+
code_parser.print_help()
|
|
157
|
+
sys.exit(1)
|
|
158
|
+
|
|
159
|
+
if args.code_command == "run":
|
|
160
|
+
from clerk.development.code_runner import main_with_args
|
|
161
|
+
|
|
162
|
+
main_with_args(project_root)
|
|
163
|
+
|
|
111
164
|
|
|
112
165
|
if __name__ == "__main__":
|
|
113
166
|
main()
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""Code runner module for testing custom code with payloads."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
from importlib import import_module
|
|
7
|
+
import importlib.util
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.prompt import Confirm, Prompt
|
|
12
|
+
from rich import print as rprint
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _generate_structured_data_code(structured_data_class) -> str:
|
|
18
|
+
"""Generate code for StructuredData initialization with all fields.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
structured_data_class: The StructuredData class from schema
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
String with indented field assignments
|
|
25
|
+
"""
|
|
26
|
+
from typing import get_origin, get_args
|
|
27
|
+
from pydantic import BaseModel
|
|
28
|
+
|
|
29
|
+
lines = []
|
|
30
|
+
|
|
31
|
+
# Get model fields
|
|
32
|
+
if hasattr(structured_data_class, 'model_fields'):
|
|
33
|
+
fields = structured_data_class.model_fields
|
|
34
|
+
|
|
35
|
+
for field_name, field_info in fields.items():
|
|
36
|
+
annotation = field_info.annotation
|
|
37
|
+
|
|
38
|
+
# Check if it's a List type
|
|
39
|
+
origin = get_origin(annotation)
|
|
40
|
+
if origin is list:
|
|
41
|
+
lines.append(f" {field_name}=[],")
|
|
42
|
+
# Check if it's an Optional type
|
|
43
|
+
elif origin is type(None) or (hasattr(annotation, '__origin__') and annotation.__origin__ is type(None)):
|
|
44
|
+
lines.append(f" {field_name}=None,")
|
|
45
|
+
# Check if the annotation is a BaseModel subclass
|
|
46
|
+
else:
|
|
47
|
+
# Try to check if it's a BaseModel (handle Optional types)
|
|
48
|
+
actual_type = annotation
|
|
49
|
+
if origin:
|
|
50
|
+
# For Optional[Type], get the actual type
|
|
51
|
+
args = get_args(annotation)
|
|
52
|
+
if args:
|
|
53
|
+
# Filter out NoneType
|
|
54
|
+
non_none_args = [arg for arg in args if arg is not type(None)]
|
|
55
|
+
if non_none_args:
|
|
56
|
+
actual_type = non_none_args[0]
|
|
57
|
+
|
|
58
|
+
# Check if actual_type is a class and subclass of BaseModel
|
|
59
|
+
try:
|
|
60
|
+
if isinstance(actual_type, type) and issubclass(actual_type, BaseModel):
|
|
61
|
+
class_name = actual_type.__name__
|
|
62
|
+
lines.append(f" {field_name}={class_name}(),")
|
|
63
|
+
else:
|
|
64
|
+
lines.append(f" {field_name}=None,")
|
|
65
|
+
except (TypeError, AttributeError):
|
|
66
|
+
lines.append(f" {field_name}=None,")
|
|
67
|
+
|
|
68
|
+
return "\n".join(lines)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def find_test_payloads(project_root: Path) -> list[Path]:
|
|
72
|
+
"""Find all test payload Python files in test/payloads directory.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
project_root: Project root directory
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of Path objects for payload files
|
|
79
|
+
"""
|
|
80
|
+
payload_dir = project_root / "test" / "payloads"
|
|
81
|
+
|
|
82
|
+
if not payload_dir.exists():
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
# Find all .py files except __init__.py
|
|
86
|
+
return [p for p in payload_dir.glob("*.py") if p.name != "__init__.py"]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def create_test_payload_template(project_root: Path) -> Path:
|
|
90
|
+
"""Create a template test payload Python file.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
project_root: Project root directory
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Path to the created template file
|
|
97
|
+
"""
|
|
98
|
+
payload_dir = project_root / "test" / "payloads"
|
|
99
|
+
payload_dir.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
|
|
101
|
+
# Check if schema exists
|
|
102
|
+
schema_path = project_root / "src" / "schema.py"
|
|
103
|
+
if not schema_path.exists():
|
|
104
|
+
console.print("[red]x[/red] No schema found. Run 'clerk schema fetch' first.")
|
|
105
|
+
console.print("[dim]Cannot generate test payload without schema.[/dim]")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
console.print("[green]✓[/green] Found schema at src/schema.py")
|
|
109
|
+
|
|
110
|
+
# Load schema to generate structured data template
|
|
111
|
+
structured_data_code = None
|
|
112
|
+
try:
|
|
113
|
+
# Add src to path
|
|
114
|
+
src_path = str(project_root / "src")
|
|
115
|
+
if src_path not in sys.path:
|
|
116
|
+
sys.path.insert(0, src_path)
|
|
117
|
+
|
|
118
|
+
spec = importlib.util.spec_from_file_location("schema", schema_path)
|
|
119
|
+
if spec and spec.loader:
|
|
120
|
+
schema_module = importlib.util.module_from_spec(spec)
|
|
121
|
+
spec.loader.exec_module(schema_module)
|
|
122
|
+
|
|
123
|
+
if hasattr(schema_module, "StructuredData"):
|
|
124
|
+
structured_data_class = getattr(schema_module, "StructuredData")
|
|
125
|
+
# Generate code with all fields
|
|
126
|
+
structured_data_code = _generate_structured_data_code(structured_data_class)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
console.print(f"[red]x[/red] Could not load schema: {str(e)}")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
if not structured_data_code:
|
|
132
|
+
console.print("[red]x[/red] Could not generate structured data code from schema.")
|
|
133
|
+
sys.exit(1)
|
|
134
|
+
|
|
135
|
+
# Get name from user
|
|
136
|
+
name = Prompt.ask(
|
|
137
|
+
"Enter a name for this test payload",
|
|
138
|
+
default="test_payload_1"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Ensure .py extension
|
|
142
|
+
if not name.endswith(".py"):
|
|
143
|
+
name = f"{name}.py"
|
|
144
|
+
|
|
145
|
+
payload_path = payload_dir / name
|
|
146
|
+
|
|
147
|
+
# Load and populate template
|
|
148
|
+
template_dir = Path(__file__).parent / "templates"
|
|
149
|
+
template_path = template_dir / "test_payload.py.template"
|
|
150
|
+
template_code = template_path.read_text(encoding="utf-8")
|
|
151
|
+
# Replace placeholder with actual fields
|
|
152
|
+
template_code = template_code.replace("{structured_data_fields}", structured_data_code)
|
|
153
|
+
|
|
154
|
+
# Write the template
|
|
155
|
+
with open(payload_path, "w", encoding="utf-8") as f:
|
|
156
|
+
f.write(template_code)
|
|
157
|
+
|
|
158
|
+
console.print(f"\n[green]✓[/green] Created template payload: {payload_path}")
|
|
159
|
+
console.print("\n[yellow]Please edit this file to customize your test data before continuing.[/yellow]")
|
|
160
|
+
|
|
161
|
+
return payload_path
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def select_payload(payloads: list[Path]) -> Path:
|
|
165
|
+
"""Let user select a payload by number.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
payloads: List of payload file paths
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Selected payload path
|
|
172
|
+
"""
|
|
173
|
+
console.print("\n[bold]Available test payloads:[/bold]")
|
|
174
|
+
for i, payload in enumerate(payloads, 1):
|
|
175
|
+
console.print(f" [cyan]{i}[/cyan]. {payload.stem}")
|
|
176
|
+
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
choice = Prompt.ask(
|
|
180
|
+
"\nSelect a payload",
|
|
181
|
+
default="1"
|
|
182
|
+
)
|
|
183
|
+
idx = int(choice) - 1
|
|
184
|
+
if 0 <= idx < len(payloads):
|
|
185
|
+
return payloads[idx]
|
|
186
|
+
else:
|
|
187
|
+
console.print(f"[red]Please enter a number between 1 and {len(payloads)}[/red]")
|
|
188
|
+
except ValueError:
|
|
189
|
+
console.print("[red]Please enter a valid number[/red]")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def load_payload(payload_path: Path, project_root: Path):
|
|
193
|
+
"""Load payload from Python module.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
payload_path: Path to payload Python file
|
|
197
|
+
project_root: Project root directory
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
ClerkCodePayload object
|
|
201
|
+
"""
|
|
202
|
+
# Add project root and src to path so imports work
|
|
203
|
+
project_root_str = str(project_root)
|
|
204
|
+
src_path = str(project_root / "src")
|
|
205
|
+
|
|
206
|
+
if project_root_str not in sys.path:
|
|
207
|
+
sys.path.insert(0, project_root_str)
|
|
208
|
+
if src_path not in sys.path:
|
|
209
|
+
sys.path.insert(0, src_path)
|
|
210
|
+
|
|
211
|
+
# Load the payload module
|
|
212
|
+
spec = importlib.util.spec_from_file_location(
|
|
213
|
+
f"test_payload_{payload_path.stem}",
|
|
214
|
+
payload_path
|
|
215
|
+
)
|
|
216
|
+
if not spec or not spec.loader:
|
|
217
|
+
raise ImportError(f"Could not load payload module from {payload_path}")
|
|
218
|
+
|
|
219
|
+
payload_module = importlib.util.module_from_spec(spec)
|
|
220
|
+
spec.loader.exec_module(payload_module)
|
|
221
|
+
|
|
222
|
+
# Get the payload object
|
|
223
|
+
if not hasattr(payload_module, "payload"):
|
|
224
|
+
raise AttributeError(f"Payload module must define a 'payload' variable")
|
|
225
|
+
|
|
226
|
+
return payload_module.payload
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def run_main_with_payload(project_root: Path, payload_path: Path):
|
|
230
|
+
"""Run main() from src/main.py with the selected payload.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
project_root: Project root directory
|
|
234
|
+
payload_path: Path to the payload Python file
|
|
235
|
+
"""
|
|
236
|
+
console.print()
|
|
237
|
+
console.print(Panel(
|
|
238
|
+
f"[bold]Running main() with payload: {payload_path.name}[/bold]",
|
|
239
|
+
style="cyan"
|
|
240
|
+
))
|
|
241
|
+
|
|
242
|
+
# Load payload
|
|
243
|
+
try:
|
|
244
|
+
payload_obj = load_payload(payload_path, project_root)
|
|
245
|
+
console.print("[green]✓[/green] Loaded payload")
|
|
246
|
+
except Exception as e:
|
|
247
|
+
console.print(f"[red]x[/red] Failed to load payload: {str(e)}")
|
|
248
|
+
import traceback
|
|
249
|
+
console.print("[dim]" + traceback.format_exc() + "[/dim]")
|
|
250
|
+
sys.exit(1)
|
|
251
|
+
|
|
252
|
+
# Find main.py
|
|
253
|
+
main_path = project_root / "src" / "main.py"
|
|
254
|
+
if not main_path.exists():
|
|
255
|
+
console.print(f"[red]x[/red] main.py not found at {main_path}")
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
# Add src to path
|
|
259
|
+
src_path = str(project_root / "src")
|
|
260
|
+
if src_path not in sys.path:
|
|
261
|
+
sys.path.insert(0, src_path)
|
|
262
|
+
|
|
263
|
+
# Start debugpy server and wait for VS Code to attach
|
|
264
|
+
import debugpy
|
|
265
|
+
|
|
266
|
+
debug_port = 5678
|
|
267
|
+
|
|
268
|
+
# Check if already running under debugger
|
|
269
|
+
if not debugpy.is_client_connected():
|
|
270
|
+
console.print(f"\n[cyan]Starting debug server on port {debug_port}...[/cyan]")
|
|
271
|
+
debugpy.listen(("localhost", debug_port))
|
|
272
|
+
|
|
273
|
+
console.print()
|
|
274
|
+
console.print("[bold yellow]⚡ Ready for debugging![/bold yellow]")
|
|
275
|
+
console.print()
|
|
276
|
+
console.print("[bold]To start debugging:[/bold]")
|
|
277
|
+
console.print(" [cyan]→ Press F5 in VS Code[/cyan]")
|
|
278
|
+
console.print(" [dim]or select 'Clerk: Debug Code Run' from the debug panel[/dim]")
|
|
279
|
+
console.print()
|
|
280
|
+
console.print("[dim]Press Ctrl+C to skip debugging and run without debugger[/dim]\n")
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
debugpy.wait_for_client()
|
|
284
|
+
console.print("[green]✓[/green] Debugger attached!\n")
|
|
285
|
+
except KeyboardInterrupt:
|
|
286
|
+
console.print("\n[yellow]Skipping debugger, running without debug...[/yellow]\n")
|
|
287
|
+
else:
|
|
288
|
+
console.print("\n[green]✓[/green] Already running under debugger\n")
|
|
289
|
+
|
|
290
|
+
# Import and run
|
|
291
|
+
try:
|
|
292
|
+
console.print()
|
|
293
|
+
console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
|
|
294
|
+
console.print("[bold cyan] Starting Execution [/bold cyan]")
|
|
295
|
+
console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
|
|
296
|
+
console.print()
|
|
297
|
+
|
|
298
|
+
# Import main module
|
|
299
|
+
spec = importlib.util.spec_from_file_location("main", main_path)
|
|
300
|
+
if spec and spec.loader:
|
|
301
|
+
main_module = importlib.util.module_from_spec(spec)
|
|
302
|
+
spec.loader.exec_module(main_module)
|
|
303
|
+
|
|
304
|
+
# Call main
|
|
305
|
+
if hasattr(main_module, "main"):
|
|
306
|
+
# Run main with the loaded payload
|
|
307
|
+
result = main_module.main(payload_obj)
|
|
308
|
+
|
|
309
|
+
console.print()
|
|
310
|
+
console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
|
|
311
|
+
console.print("[bold cyan] Execution Complete [/bold cyan]")
|
|
312
|
+
console.print("[bold cyan]═══════════════════════════════════════════════════════[/bold cyan]")
|
|
313
|
+
console.print()
|
|
314
|
+
|
|
315
|
+
# Show result
|
|
316
|
+
if result:
|
|
317
|
+
console.print("[bold]Result:[/bold]")
|
|
318
|
+
console.print(Panel(
|
|
319
|
+
f"Document ID: {result.document.id}\n"
|
|
320
|
+
f"Run ID: {result.run_id}",
|
|
321
|
+
title="Execution Result",
|
|
322
|
+
style="green"
|
|
323
|
+
))
|
|
324
|
+
|
|
325
|
+
# Show updated structured_data
|
|
326
|
+
if result.structured_data:
|
|
327
|
+
console.print("\n[bold]Updated Structured Data:[/bold]")
|
|
328
|
+
rprint(result.structured_data)
|
|
329
|
+
else:
|
|
330
|
+
console.print("[yellow]![/yellow] No result returned")
|
|
331
|
+
else:
|
|
332
|
+
console.print(f"[red]x[/red] No main() function found in {main_path}")
|
|
333
|
+
sys.exit(1)
|
|
334
|
+
else:
|
|
335
|
+
console.print(f"[red]x[/red] Could not load {main_path}")
|
|
336
|
+
sys.exit(1)
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
console.print()
|
|
340
|
+
console.print(f"[red]x Error during execution:[/red] {str(e)}")
|
|
341
|
+
import traceback
|
|
342
|
+
console.print("[dim]" + traceback.format_exc() + "[/dim]")
|
|
343
|
+
sys.exit(1)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main_with_args(project_root: Path):
|
|
347
|
+
"""Main entry point for code runner.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
project_root: Project root directory
|
|
351
|
+
"""
|
|
352
|
+
console.print()
|
|
353
|
+
console.print(Panel(
|
|
354
|
+
"[bold]Clerk Code Runner[/bold]\n"
|
|
355
|
+
"Run your custom code with test payloads",
|
|
356
|
+
style="cyan"
|
|
357
|
+
))
|
|
358
|
+
|
|
359
|
+
# Find payloads
|
|
360
|
+
payloads = find_test_payloads(project_root)
|
|
361
|
+
|
|
362
|
+
if not payloads:
|
|
363
|
+
console.print("\n[yellow]No test payloads found in test/payloads/[/yellow]")
|
|
364
|
+
|
|
365
|
+
if Confirm.ask("Would you like to generate a template payload?", default=True):
|
|
366
|
+
payload_path = create_test_payload_template(project_root)
|
|
367
|
+
|
|
368
|
+
console.print("\n[bold]Next steps:[/bold]")
|
|
369
|
+
console.print(f"1. Edit {payload_path} with your test data")
|
|
370
|
+
console.print("2. Run [cyan]clerk code run[/cyan] again to execute")
|
|
371
|
+
return
|
|
372
|
+
else:
|
|
373
|
+
console.print("\n[dim]Create a Python file in test/payloads/ and run again.[/dim]")
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
# Show available payloads
|
|
377
|
+
console.print(f"\n[green]✓[/green] Found {len(payloads)} test payload(s)")
|
|
378
|
+
|
|
379
|
+
# Let user select
|
|
380
|
+
selected_payload = select_payload(payloads)
|
|
381
|
+
console.print(f"\n[green]→[/green] Selected: {selected_payload.name}")
|
|
382
|
+
|
|
383
|
+
# Run with selected payload
|
|
384
|
+
run_main_with_payload(project_root, selected_payload)
|