fleet-python 0.2.61__tar.gz → 0.2.63__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.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- {fleet_python-0.2.61/fleet_python.egg-info → fleet_python-0.2.63}/PKG-INFO +1 -1
- fleet_python-0.2.63/examples/import_tasks.py +313 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/tasks.py +14 -4
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/tasks.py +14 -4
- {fleet_python-0.2.61 → fleet_python-0.2.63/fleet_python.egg-info}/PKG-INFO +1 -1
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet_python.egg-info/SOURCES.txt +3 -1
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet_python.egg-info/top_level.txt +1 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/pyproject.toml +1 -1
- fleet_python-0.2.63/tests/__init__.py +1 -0
- fleet_python-0.2.63/tests/test_verifier_from_string.py +420 -0
- fleet_python-0.2.61/examples/import_tasks.py +0 -102
- {fleet_python-0.2.61 → fleet_python-0.2.63}/LICENSE +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/README.md +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/diff_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/dsl_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/exampleResume.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_account.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_action_log.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_mcp_anthropic.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_mcp_openai.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_sync.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_task.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_tasks.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/example_verifier.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/export_tasks.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/gemini_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/json_tasks_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/nova_act_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/openai_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/openai_simple_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/query_builder_example.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/quickstart.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/examples/test_cdp_logging.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/env/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/env/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/exceptions.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/global_client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/instance/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/instance/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/instance/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/models.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/resources/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/resources/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/resources/browser.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/resources/mcp.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/resources/sqlite.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/verifiers/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/verifiers/bundler.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/_async/verifiers/verifier.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/config.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/env/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/env/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/exceptions.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/global_client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/instance/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/instance/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/instance/client.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/instance/models.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/models.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/resources/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/resources/base.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/resources/browser.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/resources/mcp.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/resources/sqlite.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/types.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/__init__.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/bundler.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/code.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/db.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/decorator.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/parse.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/sql_differ.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet/verifiers/verifier.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet_python.egg-info/dependency_links.txt +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/fleet_python.egg-info/requires.txt +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/scripts/fix_sync_imports.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/scripts/unasync.py +0 -0
- {fleet_python-0.2.61 → fleet_python-0.2.63}/setup.cfg +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Dict, List, Tuple
|
|
7
|
+
import fleet
|
|
8
|
+
from fleet._async.tasks import Task
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
load_dotenv()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def run_verifier_sanity_check(
|
|
15
|
+
tasks: List[Task],
|
|
16
|
+
client: fleet.AsyncFleet,
|
|
17
|
+
) -> Tuple[bool, Dict[str, str]]:
|
|
18
|
+
"""
|
|
19
|
+
Run sanity check by spinning up instances and running verifiers.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
tasks: List of Task objects to verify
|
|
23
|
+
client: AsyncFleet client instance
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Tuple of (all_passed, error_dict) where error_dict maps task_key to error message
|
|
27
|
+
"""
|
|
28
|
+
print("\n" + "=" * 60)
|
|
29
|
+
print("Running verifier sanity check...")
|
|
30
|
+
print("=" * 60)
|
|
31
|
+
|
|
32
|
+
# Group tasks by env_key×env_version×data_key×data_version
|
|
33
|
+
instance_groups = defaultdict(list)
|
|
34
|
+
for task in tasks:
|
|
35
|
+
# Build the instance key
|
|
36
|
+
env_key = task.env_id or ""
|
|
37
|
+
env_version = task.version or ""
|
|
38
|
+
data_key = task.data_id or ""
|
|
39
|
+
data_version = task.data_version or ""
|
|
40
|
+
|
|
41
|
+
instance_key = f"{env_key}×{env_version}×{data_key}×{data_version}"
|
|
42
|
+
instance_groups[instance_key].append(task)
|
|
43
|
+
|
|
44
|
+
print(f"\nFound {len(instance_groups)} unique environment/data combinations:")
|
|
45
|
+
for instance_key, group_tasks in instance_groups.items():
|
|
46
|
+
print(f" {instance_key}: {len(group_tasks)} task(s)")
|
|
47
|
+
|
|
48
|
+
# Create all instances in parallel
|
|
49
|
+
print(f"\nCreating {len(instance_groups)} instance(s) in parallel...")
|
|
50
|
+
instance_map = {}
|
|
51
|
+
|
|
52
|
+
async def create_instance(instance_key: str) -> Tuple[str, object]:
|
|
53
|
+
"""Create a single instance."""
|
|
54
|
+
try:
|
|
55
|
+
env_key, env_version, data_key, data_version = instance_key.split("×")
|
|
56
|
+
|
|
57
|
+
# Build env_key_str and data_key_str
|
|
58
|
+
if env_version:
|
|
59
|
+
env_key_str = f"{env_key}:{env_version}"
|
|
60
|
+
else:
|
|
61
|
+
env_key_str = env_key
|
|
62
|
+
|
|
63
|
+
if data_key and data_version:
|
|
64
|
+
data_key_str = f"{data_key}:{data_version}"
|
|
65
|
+
elif data_key:
|
|
66
|
+
data_key_str = data_key
|
|
67
|
+
else:
|
|
68
|
+
data_key_str = None
|
|
69
|
+
|
|
70
|
+
print(
|
|
71
|
+
f" Creating instance: {env_key_str}"
|
|
72
|
+
+ (f" with data {data_key_str}" if data_key_str else "")
|
|
73
|
+
)
|
|
74
|
+
env = await client.make(env_key=env_key_str, data_key=data_key_str)
|
|
75
|
+
return instance_key, env
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print(f" ✗ Failed to create instance for {instance_key}: {e}")
|
|
78
|
+
return instance_key, None
|
|
79
|
+
|
|
80
|
+
# Create instances concurrently
|
|
81
|
+
instance_results = await asyncio.gather(
|
|
82
|
+
*[create_instance(key) for key in instance_groups.keys()],
|
|
83
|
+
return_exceptions=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
for result in instance_results:
|
|
87
|
+
if isinstance(result, Exception):
|
|
88
|
+
print(f" ✗ Exception creating instance: {result}")
|
|
89
|
+
return False, {"__instance_creation__": str(result)}
|
|
90
|
+
instance_key, env = result
|
|
91
|
+
if env is None:
|
|
92
|
+
return False, {instance_key: "Failed to create instance"}
|
|
93
|
+
instance_map[instance_key] = env
|
|
94
|
+
|
|
95
|
+
print(f"✓ Created {len(instance_map)} instance(s)")
|
|
96
|
+
|
|
97
|
+
# Run all verifiers in parallel with concurrency limit
|
|
98
|
+
max_concurrent_verifiers = 5 # Limit concurrent verifier executions
|
|
99
|
+
print(
|
|
100
|
+
f"\nRunning {len(tasks)} verifier(s) in parallel (max {max_concurrent_verifiers} concurrent)..."
|
|
101
|
+
)
|
|
102
|
+
errors = {}
|
|
103
|
+
semaphore = asyncio.Semaphore(max_concurrent_verifiers)
|
|
104
|
+
|
|
105
|
+
async def run_single_verifier(task, instance_key: str) -> Tuple[str, bool, str]:
|
|
106
|
+
"""Run a single verifier and return (task_key, success, error_message)."""
|
|
107
|
+
async with semaphore:
|
|
108
|
+
try:
|
|
109
|
+
env = instance_map[instance_key]
|
|
110
|
+
task_key = task.key
|
|
111
|
+
|
|
112
|
+
# Run the verifier
|
|
113
|
+
if task.verifier is None:
|
|
114
|
+
return task_key, False, "No verifier found"
|
|
115
|
+
|
|
116
|
+
result = await task.verify_async(env)
|
|
117
|
+
|
|
118
|
+
# For sanity check: we expect verifiers to return 0.0 (TASK_FAILED_SCORE)
|
|
119
|
+
# since we're running on fresh instances with no task completion.
|
|
120
|
+
# This confirms the verifier runs without errors.
|
|
121
|
+
if isinstance(result, float):
|
|
122
|
+
if result == 0.0:
|
|
123
|
+
print(f" ✓ {task_key}: {result:.2f} (correctly returns 0.0)")
|
|
124
|
+
return task_key, True, ""
|
|
125
|
+
else:
|
|
126
|
+
print(
|
|
127
|
+
f" ⚠ {task_key}: {result:.2f} (expected 0.0 on fresh instance)"
|
|
128
|
+
)
|
|
129
|
+
return (
|
|
130
|
+
task_key,
|
|
131
|
+
False,
|
|
132
|
+
f"Expected 0.0 but got {result:.2f} on fresh instance",
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
# Non-float result - verifier ran but didn't return expected type
|
|
136
|
+
print(f" ⚠ {task_key}: {result} (expected float 0.0)")
|
|
137
|
+
return (
|
|
138
|
+
task_key,
|
|
139
|
+
False,
|
|
140
|
+
f"Expected float 0.0 but got {type(result).__name__}: {result}",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
task_key = task.key
|
|
145
|
+
error_msg = f"{type(e).__name__}: {str(e)}"
|
|
146
|
+
print(f" ✗ {task_key}: {error_msg}")
|
|
147
|
+
return task_key, False, error_msg
|
|
148
|
+
|
|
149
|
+
# Run verifiers concurrently with semaphore
|
|
150
|
+
verifier_results = await asyncio.gather(
|
|
151
|
+
*[
|
|
152
|
+
run_single_verifier(task, instance_key)
|
|
153
|
+
for instance_key, group_tasks in instance_groups.items()
|
|
154
|
+
for task in group_tasks
|
|
155
|
+
],
|
|
156
|
+
return_exceptions=True,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Process results
|
|
160
|
+
for result in verifier_results:
|
|
161
|
+
if isinstance(result, Exception):
|
|
162
|
+
print(f" ✗ Exception running verifier: {result}")
|
|
163
|
+
errors["__verifier_exception__"] = str(result)
|
|
164
|
+
else:
|
|
165
|
+
task_key, success, error_msg = result
|
|
166
|
+
if not success:
|
|
167
|
+
errors[task_key] = error_msg
|
|
168
|
+
|
|
169
|
+
# Clean up instances
|
|
170
|
+
print(f"\nCleaning up {len(instance_map)} instance(s)...")
|
|
171
|
+
cleanup_tasks = [env.close() for env in instance_map.values()]
|
|
172
|
+
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
|
173
|
+
print("✓ Cleanup complete")
|
|
174
|
+
|
|
175
|
+
# Summary
|
|
176
|
+
passed_count = len(tasks) - len(errors)
|
|
177
|
+
print("\n" + "=" * 60)
|
|
178
|
+
print(f"Sanity check complete: {passed_count}/{len(tasks)} passed")
|
|
179
|
+
|
|
180
|
+
if errors:
|
|
181
|
+
print(f"\n✗ {len(errors)} verifier(s) failed:")
|
|
182
|
+
for task_key, error_msg in list(errors.items())[:10]:
|
|
183
|
+
print(f" - {task_key}: {error_msg}")
|
|
184
|
+
if len(errors) > 10:
|
|
185
|
+
print(f" ... and {len(errors) - 10} more")
|
|
186
|
+
print("\nFix the verifiers and try again.")
|
|
187
|
+
return False, errors
|
|
188
|
+
else:
|
|
189
|
+
print("✓ All verifiers passed!")
|
|
190
|
+
return True, {}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def main():
|
|
194
|
+
parser = argparse.ArgumentParser(description="Import tasks from a JSON file")
|
|
195
|
+
parser.add_argument("json_file", help="Path to the JSON file containing tasks")
|
|
196
|
+
parser.add_argument(
|
|
197
|
+
"--project-key",
|
|
198
|
+
"-p",
|
|
199
|
+
help="Optional project key to associate with the tasks",
|
|
200
|
+
default=None,
|
|
201
|
+
)
|
|
202
|
+
parser.add_argument(
|
|
203
|
+
"--yes",
|
|
204
|
+
"-y",
|
|
205
|
+
action="store_true",
|
|
206
|
+
help="Skip confirmation prompt and import automatically",
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument(
|
|
209
|
+
"--skip-sanity-check",
|
|
210
|
+
action="store_true",
|
|
211
|
+
help="Skip the verifier sanity check (not recommended)",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
args = parser.parse_args()
|
|
215
|
+
|
|
216
|
+
# Load and parse the JSON file
|
|
217
|
+
try:
|
|
218
|
+
with open(args.json_file, "r", encoding="utf-8") as f:
|
|
219
|
+
tasks_data = json.load(f)
|
|
220
|
+
except FileNotFoundError:
|
|
221
|
+
print(f"Error: File '{args.json_file}' not found")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
except json.JSONDecodeError as e:
|
|
224
|
+
print(f"Error: Invalid JSON in '{args.json_file}': {e}")
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
# Extract task information and validate verifier_func
|
|
228
|
+
task_count = len(tasks_data)
|
|
229
|
+
task_keys = []
|
|
230
|
+
missing_verifier = []
|
|
231
|
+
for task_data in tasks_data:
|
|
232
|
+
task_key = task_data.get("key") or task_data.get("id")
|
|
233
|
+
if task_key:
|
|
234
|
+
task_keys.append(task_key)
|
|
235
|
+
else:
|
|
236
|
+
task_keys.append("(no key)")
|
|
237
|
+
|
|
238
|
+
# Check for verifier_func
|
|
239
|
+
verifier_code = task_data.get("verifier_func") or task_data.get("verifier_code")
|
|
240
|
+
if not verifier_code:
|
|
241
|
+
missing_verifier.append(task_key or "(no key)")
|
|
242
|
+
|
|
243
|
+
# Validate all tasks have verifier_func
|
|
244
|
+
if missing_verifier:
|
|
245
|
+
print(f"✗ Error: {len(missing_verifier)} task(s) missing verifier_func:")
|
|
246
|
+
for key in missing_verifier[:10]: # Show first 10
|
|
247
|
+
print(f" - {key}")
|
|
248
|
+
if len(missing_verifier) > 10:
|
|
249
|
+
print(f" ... and {len(missing_verifier) - 10} more")
|
|
250
|
+
print("\nAll tasks must have a verifier_func to be imported.")
|
|
251
|
+
sys.exit(1)
|
|
252
|
+
|
|
253
|
+
# Get account info
|
|
254
|
+
account = await fleet.env.account_async()
|
|
255
|
+
|
|
256
|
+
# Print summary
|
|
257
|
+
print(f"Importing to team: {account.team_name}")
|
|
258
|
+
print(f"\nFound {task_count} task(s) in '{args.json_file}':")
|
|
259
|
+
print("\nTask keys:")
|
|
260
|
+
for i, key in enumerate(task_keys, 1):
|
|
261
|
+
print(f" {i}. {key}")
|
|
262
|
+
|
|
263
|
+
if args.project_key:
|
|
264
|
+
print(f"\nProject key: {args.project_key}")
|
|
265
|
+
else:
|
|
266
|
+
print("\nProject key: (none)")
|
|
267
|
+
|
|
268
|
+
# Load tasks as Task objects
|
|
269
|
+
client = fleet.AsyncFleet()
|
|
270
|
+
tasks = []
|
|
271
|
+
print("\nLoading tasks...")
|
|
272
|
+
for task_data in tasks_data:
|
|
273
|
+
try:
|
|
274
|
+
task = await client.load_task_from_json(
|
|
275
|
+
task_data, raise_on_verifier_error=True
|
|
276
|
+
)
|
|
277
|
+
tasks.append(task)
|
|
278
|
+
except Exception as e:
|
|
279
|
+
task_key = task_data.get("key") or task_data.get("id", "unknown")
|
|
280
|
+
print(f"✗ Failed to load task {task_key}: {e}")
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
print(f"✓ Loaded {len(tasks)} tasks")
|
|
283
|
+
|
|
284
|
+
# Run sanity check (unless skipped)
|
|
285
|
+
if not args.skip_sanity_check:
|
|
286
|
+
success, errors = await run_verifier_sanity_check(tasks, client)
|
|
287
|
+
if not success:
|
|
288
|
+
sys.exit(1)
|
|
289
|
+
else:
|
|
290
|
+
print("\n⚠️ Skipping sanity check (--skip-sanity-check)")
|
|
291
|
+
|
|
292
|
+
# Confirmation prompt (unless --yes flag is provided)
|
|
293
|
+
if not args.yes:
|
|
294
|
+
print("\n" + "=" * 60)
|
|
295
|
+
response = input("Type 'YES' to proceed with import: ")
|
|
296
|
+
if response != "YES":
|
|
297
|
+
print("Import cancelled.")
|
|
298
|
+
sys.exit(0)
|
|
299
|
+
|
|
300
|
+
# Import tasks
|
|
301
|
+
print("\nImporting tasks...")
|
|
302
|
+
try:
|
|
303
|
+
results = await fleet.import_tasks_async(
|
|
304
|
+
args.json_file, project_key=args.project_key
|
|
305
|
+
)
|
|
306
|
+
print(f"\n✓ Successfully imported {len(results)} task(s)")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print(f"\n✗ Error importing tasks: {e}")
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
asyncio.run(main())
|
|
@@ -274,10 +274,18 @@ def verifier_from_string(
|
|
|
274
274
|
"""
|
|
275
275
|
try:
|
|
276
276
|
import inspect
|
|
277
|
+
import re
|
|
277
278
|
from .verifiers.verifier import AsyncVerifierFunction
|
|
278
279
|
from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
279
280
|
from fleet.verifiers.db import IgnoreConfig
|
|
280
281
|
|
|
282
|
+
# Strip @verifier decorator if present to avoid double-wrapping
|
|
283
|
+
# Remove lines like: @verifier(key="...")
|
|
284
|
+
cleaned_code = re.sub(r'@verifier\([^)]*\)\s*\n', '', verifier_func)
|
|
285
|
+
# Also remove the verifier import if present
|
|
286
|
+
cleaned_code = re.sub(r'from fleet import.*verifier.*\n', '', cleaned_code)
|
|
287
|
+
cleaned_code = re.sub(r'import.*verifier.*\n', '', cleaned_code)
|
|
288
|
+
|
|
281
289
|
# Create a local namespace for executing the code
|
|
282
290
|
local_namespace = {
|
|
283
291
|
"TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
|
|
@@ -286,13 +294,15 @@ def verifier_from_string(
|
|
|
286
294
|
"Environment": object, # Add Environment type if needed
|
|
287
295
|
}
|
|
288
296
|
|
|
289
|
-
# Execute the verifier code in the namespace
|
|
290
|
-
exec(
|
|
297
|
+
# Execute the cleaned verifier code in the namespace
|
|
298
|
+
exec(cleaned_code, globals(), local_namespace)
|
|
291
299
|
|
|
292
|
-
# Find the function that was defined
|
|
300
|
+
# Find the function that was defined (not imported)
|
|
301
|
+
# Functions defined via exec have co_filename == '<string>'
|
|
302
|
+
# Imported functions have their actual module file path
|
|
293
303
|
func_obj = None
|
|
294
304
|
for name, obj in local_namespace.items():
|
|
295
|
-
if inspect.isfunction(obj):
|
|
305
|
+
if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
|
|
296
306
|
func_obj = obj
|
|
297
307
|
break
|
|
298
308
|
|
|
@@ -265,10 +265,18 @@ def verifier_from_string(
|
|
|
265
265
|
"""
|
|
266
266
|
try:
|
|
267
267
|
import inspect
|
|
268
|
+
import re
|
|
268
269
|
from .verifiers import SyncVerifierFunction
|
|
269
270
|
from .verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
270
271
|
from .verifiers.db import IgnoreConfig
|
|
271
272
|
|
|
273
|
+
# Strip @verifier decorator if present to avoid double-wrapping
|
|
274
|
+
# Remove lines like: @verifier(key="...")
|
|
275
|
+
cleaned_code = re.sub(r'@verifier\([^)]*\)\s*\n', '', verifier_func)
|
|
276
|
+
# Also remove the verifier import if present
|
|
277
|
+
cleaned_code = re.sub(r'from fleet import.*verifier.*\n', '', cleaned_code)
|
|
278
|
+
cleaned_code = re.sub(r'import.*verifier.*\n', '', cleaned_code)
|
|
279
|
+
|
|
272
280
|
# Create a globals namespace with all required imports
|
|
273
281
|
exec_globals = globals().copy()
|
|
274
282
|
exec_globals.update(
|
|
@@ -283,13 +291,15 @@ def verifier_from_string(
|
|
|
283
291
|
# Create a local namespace for executing the code
|
|
284
292
|
local_namespace = {}
|
|
285
293
|
|
|
286
|
-
# Execute the verifier code in the namespace
|
|
287
|
-
exec(
|
|
294
|
+
# Execute the cleaned verifier code in the namespace
|
|
295
|
+
exec(cleaned_code, exec_globals, local_namespace)
|
|
288
296
|
|
|
289
|
-
# Find the function that was defined
|
|
297
|
+
# Find the function that was defined (not imported)
|
|
298
|
+
# Functions defined via exec have co_filename == '<string>'
|
|
299
|
+
# Imported functions have their actual module file path
|
|
290
300
|
func_obj = None
|
|
291
301
|
for name, obj in local_namespace.items():
|
|
292
|
-
if inspect.isfunction(obj):
|
|
302
|
+
if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
|
|
293
303
|
func_obj = obj
|
|
294
304
|
break
|
|
295
305
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests package for fleet-sdk."""
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"""Comprehensive tests for verifier_from_string function.
|
|
2
|
+
|
|
3
|
+
Tests both sync (fleet/tasks.py) and async (fleet/_async/tasks.py) versions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from fleet.tasks import verifier_from_string as sync_verifier_from_string
|
|
8
|
+
from fleet._async.tasks import verifier_from_string as async_verifier_from_string
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestSyncVerifierFromString:
|
|
12
|
+
"""Tests for sync version of verifier_from_string."""
|
|
13
|
+
|
|
14
|
+
def test_basic_verifier_without_imports(self):
|
|
15
|
+
"""Test basic verifier function without any imports."""
|
|
16
|
+
code = """
|
|
17
|
+
def my_verifier(env):
|
|
18
|
+
return 1.0
|
|
19
|
+
"""
|
|
20
|
+
verifier = sync_verifier_from_string(
|
|
21
|
+
verifier_func=code,
|
|
22
|
+
verifier_id="test-verifier",
|
|
23
|
+
verifier_key="test-key",
|
|
24
|
+
sha256="test-sha",
|
|
25
|
+
)
|
|
26
|
+
assert verifier is not None
|
|
27
|
+
assert verifier.key == "test-key"
|
|
28
|
+
assert verifier.func.__name__ == "my_verifier"
|
|
29
|
+
|
|
30
|
+
def test_verifier_with_from_fleet_import_verifier(self):
|
|
31
|
+
"""Test the bug case: 'from fleet import verifier' should not be selected."""
|
|
32
|
+
code = """
|
|
33
|
+
from fleet import verifier
|
|
34
|
+
|
|
35
|
+
def my_actual_verifier(env):
|
|
36
|
+
return 1.0
|
|
37
|
+
"""
|
|
38
|
+
verifier = sync_verifier_from_string(
|
|
39
|
+
verifier_func=code,
|
|
40
|
+
verifier_id="test-verifier",
|
|
41
|
+
verifier_key="test-key",
|
|
42
|
+
sha256="test-sha",
|
|
43
|
+
)
|
|
44
|
+
assert verifier is not None
|
|
45
|
+
# The function name should be 'my_actual_verifier', not 'verifier'
|
|
46
|
+
assert verifier.func.__name__ == "my_actual_verifier"
|
|
47
|
+
|
|
48
|
+
def test_verifier_with_from_fleet_verifiers_import_verifier(self):
|
|
49
|
+
"""Test bug case: 'from fleet.verifiers import verifier'."""
|
|
50
|
+
code = """
|
|
51
|
+
from fleet.verifiers import verifier
|
|
52
|
+
|
|
53
|
+
def check_something(env):
|
|
54
|
+
return 1.0
|
|
55
|
+
"""
|
|
56
|
+
verifier = sync_verifier_from_string(
|
|
57
|
+
verifier_func=code,
|
|
58
|
+
verifier_id="test-verifier",
|
|
59
|
+
verifier_key="test-key",
|
|
60
|
+
sha256="test-sha",
|
|
61
|
+
)
|
|
62
|
+
assert verifier is not None
|
|
63
|
+
assert verifier.func.__name__ == "check_something"
|
|
64
|
+
|
|
65
|
+
def test_verifier_with_from_fleet_verifiers_verifier_import_verifier(self):
|
|
66
|
+
"""Test bug case: 'from fleet.verifiers.verifier import verifier'."""
|
|
67
|
+
code = """
|
|
68
|
+
from fleet.verifiers.verifier import verifier
|
|
69
|
+
|
|
70
|
+
def validate_task(env):
|
|
71
|
+
return 0.5
|
|
72
|
+
"""
|
|
73
|
+
verifier = sync_verifier_from_string(
|
|
74
|
+
verifier_func=code,
|
|
75
|
+
verifier_id="test-verifier",
|
|
76
|
+
verifier_key="test-key",
|
|
77
|
+
sha256="test-sha",
|
|
78
|
+
)
|
|
79
|
+
assert verifier is not None
|
|
80
|
+
assert verifier.func.__name__ == "validate_task"
|
|
81
|
+
|
|
82
|
+
def test_verifier_with_multiple_imports(self):
|
|
83
|
+
"""Test verifier with multiple import statements."""
|
|
84
|
+
code = """
|
|
85
|
+
from fleet import verifier
|
|
86
|
+
from fleet.verifiers.db import IgnoreConfig
|
|
87
|
+
import json
|
|
88
|
+
|
|
89
|
+
def complex_verifier(env):
|
|
90
|
+
data = json.dumps({"status": "ok"})
|
|
91
|
+
return 1.0
|
|
92
|
+
"""
|
|
93
|
+
verifier = sync_verifier_from_string(
|
|
94
|
+
verifier_func=code,
|
|
95
|
+
verifier_id="test-verifier",
|
|
96
|
+
verifier_key="test-key",
|
|
97
|
+
sha256="test-sha",
|
|
98
|
+
)
|
|
99
|
+
assert verifier is not None
|
|
100
|
+
assert verifier.func.__name__ == "complex_verifier"
|
|
101
|
+
|
|
102
|
+
def test_verifier_with_legitimate_imports(self):
|
|
103
|
+
"""Test verifier with legitimate imports (not fleet-related)."""
|
|
104
|
+
code = """
|
|
105
|
+
import json
|
|
106
|
+
import os
|
|
107
|
+
|
|
108
|
+
def my_verifier(env):
|
|
109
|
+
return 1.0
|
|
110
|
+
"""
|
|
111
|
+
verifier = sync_verifier_from_string(
|
|
112
|
+
verifier_func=code,
|
|
113
|
+
verifier_id="test-verifier",
|
|
114
|
+
verifier_key="test-key",
|
|
115
|
+
sha256="test-sha",
|
|
116
|
+
)
|
|
117
|
+
assert verifier is not None
|
|
118
|
+
assert verifier.func.__name__ == "my_verifier"
|
|
119
|
+
|
|
120
|
+
def test_verifier_using_task_scores(self):
|
|
121
|
+
"""Test verifier that uses TASK_SUCCESSFUL_SCORE and TASK_FAILED_SCORE."""
|
|
122
|
+
code = """
|
|
123
|
+
def my_verifier(env):
|
|
124
|
+
if True:
|
|
125
|
+
return TASK_SUCCESSFUL_SCORE
|
|
126
|
+
return TASK_FAILED_SCORE
|
|
127
|
+
"""
|
|
128
|
+
verifier = sync_verifier_from_string(
|
|
129
|
+
verifier_func=code,
|
|
130
|
+
verifier_id="test-verifier",
|
|
131
|
+
verifier_key="test-key",
|
|
132
|
+
sha256="test-sha",
|
|
133
|
+
)
|
|
134
|
+
assert verifier is not None
|
|
135
|
+
assert verifier.func.__name__ == "my_verifier"
|
|
136
|
+
|
|
137
|
+
def test_verifier_using_ignore_config(self):
|
|
138
|
+
"""Test verifier that uses IgnoreConfig."""
|
|
139
|
+
code = """
|
|
140
|
+
def my_verifier(env):
|
|
141
|
+
config = IgnoreConfig()
|
|
142
|
+
return 1.0
|
|
143
|
+
"""
|
|
144
|
+
verifier = sync_verifier_from_string(
|
|
145
|
+
verifier_func=code,
|
|
146
|
+
verifier_id="test-verifier",
|
|
147
|
+
verifier_key="test-key",
|
|
148
|
+
sha256="test-sha",
|
|
149
|
+
)
|
|
150
|
+
assert verifier is not None
|
|
151
|
+
assert verifier.func.__name__ == "my_verifier"
|
|
152
|
+
|
|
153
|
+
def test_no_function_defined_raises_error(self):
|
|
154
|
+
"""Test that code with no function raises ValueError."""
|
|
155
|
+
code = """
|
|
156
|
+
x = 1
|
|
157
|
+
y = 2
|
|
158
|
+
"""
|
|
159
|
+
with pytest.raises(ValueError, match="No function found in verifier code"):
|
|
160
|
+
sync_verifier_from_string(
|
|
161
|
+
verifier_func=code,
|
|
162
|
+
verifier_id="test-verifier",
|
|
163
|
+
verifier_key="test-key",
|
|
164
|
+
sha256="test-sha",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def test_only_imports_no_function_raises_error(self):
|
|
168
|
+
"""Test that code with only imports and no function raises ValueError."""
|
|
169
|
+
code = """
|
|
170
|
+
from fleet import verifier
|
|
171
|
+
import json
|
|
172
|
+
"""
|
|
173
|
+
with pytest.raises(ValueError, match="No function found in verifier code"):
|
|
174
|
+
sync_verifier_from_string(
|
|
175
|
+
verifier_func=code,
|
|
176
|
+
verifier_id="test-verifier",
|
|
177
|
+
verifier_key="test-key",
|
|
178
|
+
sha256="test-sha",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def test_verifier_with_multiple_functions(self):
|
|
182
|
+
"""Test that first user-defined function is selected when multiple exist."""
|
|
183
|
+
code = """
|
|
184
|
+
def helper_function():
|
|
185
|
+
return "helper"
|
|
186
|
+
|
|
187
|
+
def my_verifier(env):
|
|
188
|
+
return 1.0
|
|
189
|
+
"""
|
|
190
|
+
verifier = sync_verifier_from_string(
|
|
191
|
+
verifier_func=code,
|
|
192
|
+
verifier_id="test-verifier",
|
|
193
|
+
verifier_key="test-key",
|
|
194
|
+
sha256="test-sha",
|
|
195
|
+
)
|
|
196
|
+
assert verifier is not None
|
|
197
|
+
# Should pick the first function (order depends on dict iteration)
|
|
198
|
+
assert verifier.func.__name__ in ["helper_function", "my_verifier"]
|
|
199
|
+
|
|
200
|
+
def test_verifier_with_decorator_usage(self):
|
|
201
|
+
"""Test verifier that would use @verifier decorator in normal usage."""
|
|
202
|
+
code = """
|
|
203
|
+
from fleet.verifiers.verifier import verifier
|
|
204
|
+
|
|
205
|
+
def actual_verifier_function(env, project_key: str = "TEST"):
|
|
206
|
+
# This is the function that should be selected
|
|
207
|
+
return 1.0
|
|
208
|
+
"""
|
|
209
|
+
verifier = sync_verifier_from_string(
|
|
210
|
+
verifier_func=code,
|
|
211
|
+
verifier_id="test-verifier",
|
|
212
|
+
verifier_key="test-key",
|
|
213
|
+
sha256="test-sha",
|
|
214
|
+
)
|
|
215
|
+
assert verifier is not None
|
|
216
|
+
assert verifier.func.__name__ == "actual_verifier_function"
|
|
217
|
+
|
|
218
|
+
def test_verifier_metadata_stored_correctly(self):
|
|
219
|
+
"""Test that verifier metadata is stored correctly."""
|
|
220
|
+
code = """
|
|
221
|
+
def my_verifier(env):
|
|
222
|
+
return 1.0
|
|
223
|
+
"""
|
|
224
|
+
sha256_val = "abcd1234"
|
|
225
|
+
verifier = sync_verifier_from_string(
|
|
226
|
+
verifier_func=code,
|
|
227
|
+
verifier_id="test-verifier-id",
|
|
228
|
+
verifier_key="test-key",
|
|
229
|
+
sha256=sha256_val,
|
|
230
|
+
)
|
|
231
|
+
assert verifier.key == "test-key"
|
|
232
|
+
assert verifier.verifier_id == "test-verifier-id"
|
|
233
|
+
assert verifier._sha256 == sha256_val
|
|
234
|
+
assert verifier._verifier_code == code
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TestAsyncVerifierFromString:
|
|
238
|
+
"""Tests for async version of verifier_from_string."""
|
|
239
|
+
|
|
240
|
+
def test_basic_async_verifier_without_imports(self):
|
|
241
|
+
"""Test basic async verifier function without any imports."""
|
|
242
|
+
code = """
|
|
243
|
+
async def my_async_verifier(env):
|
|
244
|
+
return 1.0
|
|
245
|
+
"""
|
|
246
|
+
verifier = async_verifier_from_string(
|
|
247
|
+
verifier_func=code,
|
|
248
|
+
verifier_id="test-verifier",
|
|
249
|
+
verifier_key="test-key",
|
|
250
|
+
sha256="test-sha",
|
|
251
|
+
)
|
|
252
|
+
assert verifier is not None
|
|
253
|
+
|
|
254
|
+
def test_async_verifier_with_from_fleet_import_verifier(self):
|
|
255
|
+
"""Test async bug case: 'from fleet import verifier'."""
|
|
256
|
+
code = """
|
|
257
|
+
from fleet import verifier
|
|
258
|
+
|
|
259
|
+
async def my_actual_async_verifier(env):
|
|
260
|
+
return 1.0
|
|
261
|
+
"""
|
|
262
|
+
verifier = async_verifier_from_string(
|
|
263
|
+
verifier_func=code,
|
|
264
|
+
verifier_id="test-verifier",
|
|
265
|
+
verifier_key="test-key",
|
|
266
|
+
sha256="test-sha",
|
|
267
|
+
)
|
|
268
|
+
assert verifier is not None
|
|
269
|
+
assert verifier.func.__name__ == "my_actual_async_verifier"
|
|
270
|
+
|
|
271
|
+
def test_async_verifier_with_multiple_imports(self):
|
|
272
|
+
"""Test async verifier with multiple import statements."""
|
|
273
|
+
code = """
|
|
274
|
+
from fleet import verifier
|
|
275
|
+
from fleet.verifiers.db import IgnoreConfig
|
|
276
|
+
import asyncio
|
|
277
|
+
|
|
278
|
+
async def complex_async_verifier(env):
|
|
279
|
+
await asyncio.sleep(0)
|
|
280
|
+
return 1.0
|
|
281
|
+
"""
|
|
282
|
+
verifier = async_verifier_from_string(
|
|
283
|
+
verifier_func=code,
|
|
284
|
+
verifier_id="test-verifier",
|
|
285
|
+
verifier_key="test-key",
|
|
286
|
+
sha256="test-sha",
|
|
287
|
+
)
|
|
288
|
+
assert verifier is not None
|
|
289
|
+
assert verifier.func.__name__ == "complex_async_verifier"
|
|
290
|
+
|
|
291
|
+
def test_sync_function_in_async_module(self):
|
|
292
|
+
"""Test that sync functions also work in async module."""
|
|
293
|
+
code = """
|
|
294
|
+
from fleet import verifier
|
|
295
|
+
|
|
296
|
+
def sync_verifier_in_async_module(env):
|
|
297
|
+
return 1.0
|
|
298
|
+
"""
|
|
299
|
+
verifier = async_verifier_from_string(
|
|
300
|
+
verifier_func=code,
|
|
301
|
+
verifier_id="test-verifier",
|
|
302
|
+
verifier_key="test-key",
|
|
303
|
+
sha256="test-sha",
|
|
304
|
+
)
|
|
305
|
+
assert verifier is not None
|
|
306
|
+
assert verifier.func.__name__ == "sync_verifier_in_async_module"
|
|
307
|
+
|
|
308
|
+
def test_async_no_function_defined_raises_error(self):
|
|
309
|
+
"""Test that async code with no function raises ValueError."""
|
|
310
|
+
code = """
|
|
311
|
+
x = 1
|
|
312
|
+
y = 2
|
|
313
|
+
"""
|
|
314
|
+
with pytest.raises(ValueError, match="No function found in verifier code"):
|
|
315
|
+
async_verifier_from_string(
|
|
316
|
+
verifier_func=code,
|
|
317
|
+
verifier_id="test-verifier",
|
|
318
|
+
verifier_key="test-key",
|
|
319
|
+
sha256="test-sha",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def test_async_only_imports_raises_error(self):
|
|
323
|
+
"""Test that async code with only imports raises ValueError."""
|
|
324
|
+
code = """
|
|
325
|
+
from fleet import verifier
|
|
326
|
+
import asyncio
|
|
327
|
+
"""
|
|
328
|
+
with pytest.raises(ValueError, match="No function found in verifier code"):
|
|
329
|
+
async_verifier_from_string(
|
|
330
|
+
verifier_func=code,
|
|
331
|
+
verifier_id="test-verifier",
|
|
332
|
+
verifier_key="test-key",
|
|
333
|
+
sha256="test-sha",
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class TestRealWorldScenarios:
|
|
338
|
+
"""Test real-world scenarios from the bug report."""
|
|
339
|
+
|
|
340
|
+
def test_original_bug_report_scenario(self):
|
|
341
|
+
"""Test the exact scenario from the bug report."""
|
|
342
|
+
code = """from fleet import verifier
|
|
343
|
+
def blahblah():
|
|
344
|
+
return 1.0
|
|
345
|
+
"""
|
|
346
|
+
verifier = sync_verifier_from_string(
|
|
347
|
+
verifier_func=code,
|
|
348
|
+
verifier_id="test-verifier",
|
|
349
|
+
verifier_key="test-key",
|
|
350
|
+
sha256="test-sha",
|
|
351
|
+
)
|
|
352
|
+
# Should select 'blahblah', not the imported 'verifier'
|
|
353
|
+
assert verifier.func.__name__ == "blahblah"
|
|
354
|
+
|
|
355
|
+
def test_example_verifier_pattern(self):
|
|
356
|
+
"""Test a pattern similar to example_verifier.py."""
|
|
357
|
+
code = """import fleet
|
|
358
|
+
from fleet.verifiers.verifier import verifier
|
|
359
|
+
from fleet.verifiers.db import IgnoreConfig
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def validate_finish_blue_green_deployment(
|
|
363
|
+
env, final_answer: str = None
|
|
364
|
+
) -> int:
|
|
365
|
+
'''Validate that DEBT-722 and DEBT-720 are marked as Done'''
|
|
366
|
+
return 1.0
|
|
367
|
+
"""
|
|
368
|
+
verifier = sync_verifier_from_string(
|
|
369
|
+
verifier_func=code,
|
|
370
|
+
verifier_id="test-verifier",
|
|
371
|
+
verifier_key="test-key",
|
|
372
|
+
sha256="test-sha",
|
|
373
|
+
)
|
|
374
|
+
assert verifier.func.__name__ == "validate_finish_blue_green_deployment"
|
|
375
|
+
|
|
376
|
+
def test_example_task_pattern_sync(self):
|
|
377
|
+
"""Test a pattern similar to example_task.py sync verifier."""
|
|
378
|
+
code = """from fleet.verifiers.verifier import verifier
|
|
379
|
+
from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def create_bug_issue_sync(
|
|
383
|
+
env, project_key: str = "SCRUM", issue_title: str = "Sample Bug"
|
|
384
|
+
) -> float:
|
|
385
|
+
'''Synchronous verifier for remote execution.'''
|
|
386
|
+
try:
|
|
387
|
+
return TASK_SUCCESSFUL_SCORE
|
|
388
|
+
except Exception as e:
|
|
389
|
+
return TASK_FAILED_SCORE
|
|
390
|
+
"""
|
|
391
|
+
verifier = sync_verifier_from_string(
|
|
392
|
+
verifier_func=code,
|
|
393
|
+
verifier_id="test-verifier",
|
|
394
|
+
verifier_key="test-key",
|
|
395
|
+
sha256="test-sha",
|
|
396
|
+
)
|
|
397
|
+
assert verifier.func.__name__ == "create_bug_issue_sync"
|
|
398
|
+
|
|
399
|
+
def test_example_task_pattern_async(self):
|
|
400
|
+
"""Test a pattern similar to example_task.py async verifier."""
|
|
401
|
+
code = """from fleet.verifiers.verifier import verifier
|
|
402
|
+
from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
async def create_bug_issue_async(
|
|
406
|
+
env, project_key: str = "SCRUM", issue_title: str = "Sample Bug"
|
|
407
|
+
) -> float:
|
|
408
|
+
'''Async verifier for local execution with async environments.'''
|
|
409
|
+
try:
|
|
410
|
+
return TASK_SUCCESSFUL_SCORE
|
|
411
|
+
except Exception as e:
|
|
412
|
+
return TASK_FAILED_SCORE
|
|
413
|
+
"""
|
|
414
|
+
verifier = async_verifier_from_string(
|
|
415
|
+
verifier_func=code,
|
|
416
|
+
verifier_id="test-verifier",
|
|
417
|
+
verifier_key="test-key",
|
|
418
|
+
sha256="test-sha",
|
|
419
|
+
)
|
|
420
|
+
assert verifier.func.__name__ == "create_bug_issue_async"
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import argparse
|
|
3
|
-
import json
|
|
4
|
-
import sys
|
|
5
|
-
import fleet
|
|
6
|
-
from dotenv import load_dotenv
|
|
7
|
-
|
|
8
|
-
load_dotenv()
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
async def main():
|
|
12
|
-
parser = argparse.ArgumentParser(description="Import tasks from a JSON file")
|
|
13
|
-
parser.add_argument("json_file", help="Path to the JSON file containing tasks")
|
|
14
|
-
parser.add_argument(
|
|
15
|
-
"--project-key",
|
|
16
|
-
"-p",
|
|
17
|
-
help="Optional project key to associate with the tasks",
|
|
18
|
-
default=None,
|
|
19
|
-
)
|
|
20
|
-
parser.add_argument(
|
|
21
|
-
"--yes",
|
|
22
|
-
"-y",
|
|
23
|
-
action="store_true",
|
|
24
|
-
help="Skip confirmation prompt and import automatically",
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
args = parser.parse_args()
|
|
28
|
-
|
|
29
|
-
# Load and parse the JSON file
|
|
30
|
-
try:
|
|
31
|
-
with open(args.json_file, "r", encoding="utf-8") as f:
|
|
32
|
-
tasks_data = json.load(f)
|
|
33
|
-
except FileNotFoundError:
|
|
34
|
-
print(f"Error: File '{args.json_file}' not found")
|
|
35
|
-
sys.exit(1)
|
|
36
|
-
except json.JSONDecodeError as e:
|
|
37
|
-
print(f"Error: Invalid JSON in '{args.json_file}': {e}")
|
|
38
|
-
sys.exit(1)
|
|
39
|
-
|
|
40
|
-
# Extract task information and validate verifier_func
|
|
41
|
-
task_count = len(tasks_data)
|
|
42
|
-
task_keys = []
|
|
43
|
-
missing_verifier = []
|
|
44
|
-
for task_data in tasks_data:
|
|
45
|
-
task_key = task_data.get("key") or task_data.get("id")
|
|
46
|
-
if task_key:
|
|
47
|
-
task_keys.append(task_key)
|
|
48
|
-
else:
|
|
49
|
-
task_keys.append("(no key)")
|
|
50
|
-
|
|
51
|
-
# Check for verifier_func
|
|
52
|
-
verifier_code = task_data.get("verifier_func") or task_data.get("verifier_code")
|
|
53
|
-
if not verifier_code:
|
|
54
|
-
missing_verifier.append(task_key or "(no key)")
|
|
55
|
-
|
|
56
|
-
# Validate all tasks have verifier_func
|
|
57
|
-
if missing_verifier:
|
|
58
|
-
print(f"✗ Error: {len(missing_verifier)} task(s) missing verifier_func:")
|
|
59
|
-
for key in missing_verifier[:10]: # Show first 10
|
|
60
|
-
print(f" - {key}")
|
|
61
|
-
if len(missing_verifier) > 10:
|
|
62
|
-
print(f" ... and {len(missing_verifier) - 10} more")
|
|
63
|
-
print("\nAll tasks must have a verifier_func to be imported.")
|
|
64
|
-
sys.exit(1)
|
|
65
|
-
|
|
66
|
-
# Get account info
|
|
67
|
-
account = await fleet.env.account_async()
|
|
68
|
-
|
|
69
|
-
# Print summary
|
|
70
|
-
print(f"Importing to team: {account.team_name}")
|
|
71
|
-
print(f"\nFound {task_count} task(s) in '{args.json_file}':")
|
|
72
|
-
print("\nTask keys:")
|
|
73
|
-
for i, key in enumerate(task_keys, 1):
|
|
74
|
-
print(f" {i}. {key}")
|
|
75
|
-
|
|
76
|
-
if args.project_key:
|
|
77
|
-
print(f"\nProject key: {args.project_key}")
|
|
78
|
-
else:
|
|
79
|
-
print("\nProject key: (none)")
|
|
80
|
-
|
|
81
|
-
# Confirmation prompt (unless --yes flag is provided)
|
|
82
|
-
if not args.yes:
|
|
83
|
-
print("\n" + "=" * 60)
|
|
84
|
-
response = input("Type 'YES' to proceed with import: ")
|
|
85
|
-
if response != "YES":
|
|
86
|
-
print("Import cancelled.")
|
|
87
|
-
sys.exit(0)
|
|
88
|
-
|
|
89
|
-
# Import tasks
|
|
90
|
-
print("\nImporting tasks...")
|
|
91
|
-
try:
|
|
92
|
-
results = await fleet.import_tasks_async(
|
|
93
|
-
args.json_file, project_key=args.project_key
|
|
94
|
-
)
|
|
95
|
-
print(f"\n✓ Successfully imported {len(results)} task(s)")
|
|
96
|
-
except Exception as e:
|
|
97
|
-
print(f"\n✗ Error importing tasks: {e}")
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if __name__ == "__main__":
|
|
102
|
-
asyncio.run(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|