fleet-python 0.2.60__py3-none-any.whl → 0.2.62__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fleet-python might be problematic. Click here for more details.

examples/export_tasks.py CHANGED
@@ -21,6 +21,12 @@ def main():
21
21
  help="Optional list of task keys to export (space-separated)",
22
22
  default=None,
23
23
  )
24
+ parser.add_argument(
25
+ "--task-project-key",
26
+ "-tpk",
27
+ help="Optional task project key to filter tasks",
28
+ default=None,
29
+ )
24
30
  parser.add_argument(
25
31
  "--output",
26
32
  "-o",
@@ -31,9 +37,17 @@ def main():
31
37
  args = parser.parse_args()
32
38
 
33
39
  # Validate that only one filter is specified
34
- if args.project_key and args.task_keys:
40
+ filters_specified = sum(
41
+ [
42
+ args.project_key is not None,
43
+ args.task_keys is not None,
44
+ args.task_project_key is not None,
45
+ ]
46
+ )
47
+
48
+ if filters_specified > 1:
35
49
  parser.error(
36
- "Cannot specify both --project-key and --task-keys. Use one or neither."
50
+ "Cannot specify multiple filters. Use only one of --project-key, --task-keys, or --task-project-key."
37
51
  )
38
52
 
39
53
  # Get account info
@@ -49,6 +63,9 @@ def main():
49
63
  f"Loading {len(args.task_keys)} specific task(s): {', '.join(args.task_keys)}"
50
64
  )
51
65
  tasks = fleet.load_tasks(keys=args.task_keys)
66
+ elif args.task_project_key:
67
+ print(f"Loading tasks from task project: {args.task_project_key}")
68
+ tasks = fleet.load_tasks(task_project_key=args.task_project_key)
52
69
  else:
53
70
  print("Loading all tasks")
54
71
  tasks = fleet.load_tasks()
fleet/_async/client.py CHANGED
@@ -397,6 +397,7 @@ class AsyncFleet:
397
397
  verifier_id=verifier_id, # Set verifier_id so _rebuild_verifier works
398
398
  verifier_sha=verifier_sha, # Set verifier_sha
399
399
  metadata=task_json.get("metadata", {}), # Default empty metadata
400
+ output_json_schema=task_json.get("output_json_schema"), # JSON schema for output
400
401
  )
401
402
  return task
402
403
 
@@ -407,6 +408,7 @@ class AsyncFleet:
407
408
  version: Optional[str] = None,
408
409
  team_id: Optional[str] = None,
409
410
  project_key: Optional[str] = None,
411
+ task_project_key: Optional[str] = None,
410
412
  data_id: Optional[str] = None,
411
413
  data_version: Optional[str] = None,
412
414
  ) -> List[Task]:
@@ -418,6 +420,7 @@ class AsyncFleet:
418
420
  version: Optional version to filter tasks by (client-side filter)
419
421
  team_id: Optional team_id to filter by (admin only)
420
422
  project_key: Optional project key to filter tasks by
423
+ task_project_key: Optional task project key to filter tasks by
421
424
  data_id: Optional data identifier to filter tasks by
422
425
  data_version: Optional data version to filter tasks by
423
426
 
@@ -433,6 +436,8 @@ class AsyncFleet:
433
436
  params["team_id"] = team_id
434
437
  if project_key is not None:
435
438
  params["project_key"] = project_key
439
+ if task_project_key is not None:
440
+ params["task_project_key"] = task_project_key
436
441
  if data_id is not None:
437
442
  params["data_id"] = data_id
438
443
  if data_version is not None:
@@ -547,6 +552,7 @@ class AsyncFleet:
547
552
  verifier_func=verifier_func, # Set verifier code
548
553
  verifier=verifier, # Use created verifier or None
549
554
  metadata={}, # Default empty metadata
555
+ output_json_schema=getattr(task_response, "output_json_schema", None), # Get output_json_schema if available
550
556
  )
551
557
  tasks.append(task)
552
558
 
fleet/_async/models.py CHANGED
@@ -154,6 +154,7 @@ class TaskRequest(BaseModel):
154
154
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
155
155
  version: Optional[str] = Field(None, title="Version")
156
156
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
157
+ output_json_schema: Optional[Dict[str, Any]] = Field(None, title="Output Json Schema")
157
158
 
158
159
 
159
160
  class TaskUpdateRequest(BaseModel):
@@ -180,8 +181,11 @@ class TaskResponse(BaseModel):
180
181
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
181
182
  verifier_func: Optional[str] = Field(None, title="Verifier Func")
182
183
  version: Optional[str] = Field(None, title="Version")
184
+ data_id: Optional[str] = Field(None, title="Data Id")
185
+ data_version: Optional[str] = Field(None, title="Data Version")
183
186
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
184
187
  verifier: Optional[VerifierData] = Field(None, title="Verifier")
188
+ output_json_schema: Optional[Dict[str, Any]] = Field(None, title="Output Json Schema")
185
189
 
186
190
 
187
191
  class ValidationError(BaseModel):
fleet/_async/tasks.py CHANGED
@@ -38,6 +38,9 @@ class Task(BaseModel):
38
38
  metadata: Optional[Dict[str, Any]] = Field(
39
39
  default_factory=dict, description="Additional task metadata"
40
40
  )
41
+ output_json_schema: Optional[Dict[str, Any]] = Field(
42
+ None, description="JSON schema for expected output format"
43
+ )
41
44
 
42
45
  @validator("key")
43
46
  def validate_key_format(cls, v):
@@ -286,10 +289,12 @@ def verifier_from_string(
286
289
  # Execute the verifier code in the namespace
287
290
  exec(verifier_func, globals(), local_namespace)
288
291
 
289
- # Find the function that was defined
292
+ # Find the function that was defined (not imported)
293
+ # Functions defined via exec have co_filename == '<string>'
294
+ # Imported functions have their actual module file path
290
295
  func_obj = None
291
296
  for name, obj in local_namespace.items():
292
- if inspect.isfunction(obj):
297
+ if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
293
298
  func_obj = obj
294
299
  break
295
300
 
@@ -329,6 +334,7 @@ async def load_tasks(
329
334
  version: Optional[str] = None,
330
335
  team_id: Optional[str] = None,
331
336
  project_key: Optional[str] = None,
337
+ task_project_key: Optional[str] = None,
332
338
  data_id: Optional[str] = None,
333
339
  data_version: Optional[str] = None,
334
340
  ) -> List[Task]:
@@ -340,6 +346,7 @@ async def load_tasks(
340
346
  version: Optional version to filter tasks by
341
347
  team_id: Optional team_id to filter by (admin only)
342
348
  project_key: Optional project key to filter tasks by
349
+ task_project_key: Optional task project key to filter tasks by
343
350
  data_id: Optional data identifier to filter tasks by
344
351
  data_version: Optional data version to filter tasks by
345
352
 
@@ -359,6 +366,7 @@ async def load_tasks(
359
366
  version=version,
360
367
  team_id=team_id,
361
368
  project_key=project_key,
369
+ task_project_key=task_project_key,
362
370
  data_id=data_id,
363
371
  data_version=data_version,
364
372
  )
fleet/client.py CHANGED
@@ -395,6 +395,7 @@ class Fleet:
395
395
  verifier_id=verifier_id, # Set verifier_id so _rebuild_verifier works
396
396
  verifier_sha=verifier_sha, # Set verifier_sha
397
397
  metadata=task_json.get("metadata", {}), # Default empty metadata
398
+ output_json_schema=task_json.get("output_json_schema"), # JSON schema for output
398
399
  )
399
400
  return task
400
401
 
@@ -405,6 +406,7 @@ class Fleet:
405
406
  version: Optional[str] = None,
406
407
  team_id: Optional[str] = None,
407
408
  project_key: Optional[str] = None,
409
+ task_project_key: Optional[str] = None,
408
410
  data_id: Optional[str] = None,
409
411
  data_version: Optional[str] = None,
410
412
  ) -> List[Task]:
@@ -416,6 +418,7 @@ class Fleet:
416
418
  version: Optional version to filter tasks by (client-side filter)
417
419
  team_id: Optional team_id to filter by (admin only)
418
420
  project_key: Optional project key to filter tasks by
421
+ task_project_key: Optional task project key to filter tasks by
419
422
  data_id: Optional data identifier to filter tasks by
420
423
  data_version: Optional data version to filter tasks by
421
424
 
@@ -431,6 +434,8 @@ class Fleet:
431
434
  params["team_id"] = team_id
432
435
  if project_key is not None:
433
436
  params["project_key"] = project_key
437
+ if task_project_key is not None:
438
+ params["task_project_key"] = task_project_key
434
439
  if data_id is not None:
435
440
  params["data_id"] = data_id
436
441
  if data_version is not None:
@@ -553,6 +558,7 @@ class Fleet:
553
558
  verifier_func=verifier_func, # Set verifier code
554
559
  verifier=verifier, # Use created verifier or None
555
560
  metadata={}, # Default empty metadata
561
+ output_json_schema=getattr(task_response, "output_json_schema", None), # Get output_json_schema if available
556
562
  )
557
563
  tasks.append(task)
558
564
 
fleet/models.py CHANGED
@@ -157,6 +157,9 @@ class TaskRequest(BaseModel):
157
157
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
158
158
  version: Optional[str] = Field(None, title="Version")
159
159
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
160
+ output_json_schema: Optional[Dict[str, Any]] = Field(
161
+ None, title="Output Json Schema"
162
+ )
160
163
 
161
164
 
162
165
  class TaskUpdateRequest(BaseModel):
@@ -183,8 +186,13 @@ class TaskResponse(BaseModel):
183
186
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
184
187
  verifier_func: Optional[str] = Field(None, title="Verifier Func")
185
188
  version: Optional[str] = Field(None, title="Version")
189
+ data_id: Optional[str] = Field(None, title="Data Id")
190
+ data_version: Optional[str] = Field(None, title="Data Version")
186
191
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
187
192
  verifier: Optional[VerifierData] = Field(None, title="Verifier")
193
+ output_json_schema: Optional[Dict[str, Any]] = Field(
194
+ None, title="Output Json Schema"
195
+ )
188
196
 
189
197
 
190
198
  class ValidationError(BaseModel):
fleet/tasks.py CHANGED
@@ -39,6 +39,9 @@ class Task(BaseModel):
39
39
  metadata: Optional[Dict[str, Any]] = Field(
40
40
  default_factory=dict, description="Additional task metadata"
41
41
  )
42
+ output_json_schema: Optional[Dict[str, Any]] = Field(
43
+ None, description="JSON schema for expected output format"
44
+ )
42
45
 
43
46
  @validator("key")
44
47
  def validate_key_format(cls, v):
@@ -283,10 +286,12 @@ def verifier_from_string(
283
286
  # Execute the verifier code in the namespace
284
287
  exec(verifier_func, exec_globals, local_namespace)
285
288
 
286
- # Find the function that was defined
289
+ # Find the function that was defined (not imported)
290
+ # Functions defined via exec have co_filename == '<string>'
291
+ # Imported functions have their actual module file path
287
292
  func_obj = None
288
293
  for name, obj in local_namespace.items():
289
- if inspect.isfunction(obj):
294
+ if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
290
295
  func_obj = obj
291
296
  break
292
297
 
@@ -330,6 +335,7 @@ def load_tasks(
330
335
  version: Optional[str] = None,
331
336
  team_id: Optional[str] = None,
332
337
  project_key: Optional[str] = None,
338
+ task_project_key: Optional[str] = None,
333
339
  data_id: Optional[str] = None,
334
340
  data_version: Optional[str] = None,
335
341
  ) -> List[Task]:
@@ -341,6 +347,7 @@ def load_tasks(
341
347
  version: Optional version to filter tasks by
342
348
  team_id: Optional team_id to filter by (admin only)
343
349
  project_key: Optional project key to filter tasks by
350
+ task_project_key: Optional task project key to filter tasks by
344
351
  data_id: Optional data identifier to filter tasks by
345
352
  data_version: Optional data version to filter tasks by
346
353
 
@@ -360,6 +367,7 @@ def load_tasks(
360
367
  version=version,
361
368
  team_id=team_id,
362
369
  project_key=project_key,
370
+ task_project_key=task_project_key,
363
371
  data_id=data_id,
364
372
  data_version=data_version,
365
373
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.60
3
+ Version: 0.2.62
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -11,7 +11,7 @@ examples/example_sync.py,sha256=EkuWmUzB1ZsBJQk6ZRflB793rKsuRHeSg5HJZHVhBB0,975
11
11
  examples/example_task.py,sha256=dhG6STAkNsTdHs9cO1RFH9WfuvRmq5bRC211hTeFrk8,7088
12
12
  examples/example_tasks.py,sha256=xTL8UWVAuolSX6swskfrAcmDrLIzn45dJ7YPWCwoEBU,514
13
13
  examples/example_verifier.py,sha256=0vwNITIG3m4CkSPwIxNXcGx9TqrxEsCGqK2A8keKZMM,2392
14
- examples/export_tasks.py,sha256=e5CaHga_1IV2W7PGPjQjOZtMrLAhoPNt74V6qRY99EE,2700
14
+ examples/export_tasks.py,sha256=cJ_8xND7Q3IOM1JfJPR-DH3aLfHo_KmKJeO-1IVUFrQ,3237
15
15
  examples/gemini_example.py,sha256=qj9WDazQTYNiRHNeUg9Tjkp33lJMwbx8gDfpFe1sDQo,16180
16
16
  examples/import_tasks.py,sha256=pb60dOXnOn-G1INQxV1wujeHmmY2oWz9Ddj9SbMm1pk,3139
17
17
  examples/json_tasks_example.py,sha256=CYPESGGtOo0fmsDdLidujTfsE4QlJHw7rOhyVqPJ_Ls,5329
@@ -23,20 +23,20 @@ examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
23
23
  examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
24
24
  fleet/__init__.py,sha256=yC4HIcbtPAPOgI0lLri3l8nbXkNee9JOihKAc7SXYkY,4201
25
25
  fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
26
- fleet/client.py,sha256=-bjVlA9Iw7dg9XdHVUMvXpTesxzZ_tfw8eD_1D04SW0,32057
26
+ fleet/client.py,sha256=2zbX2a_cAfrpJMp48nfiIKPFCXE04ahtmogaymibtgg,32499
27
27
  fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
28
28
  fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
29
29
  fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
30
- fleet/models.py,sha256=d9eish0KO3t4VCNu8h8Q_6K1Xj-crYo5Fejtle0Ur28,13056
31
- fleet/tasks.py,sha256=DlxQu9sftsW0jxKwdIE4_DsVxv1v3t6Mf3iKSwFQM3M,16435
30
+ fleet/models.py,sha256=-8WG10SMkXUljRqD_8pfajCr4PS9qYDVLAZ8RxdJMb0,13392
31
+ fleet/tasks.py,sha256=STR34InS14rMq1DFPsCeAASarsvUqf_ODK_jitT8ORU,16914
32
32
  fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
33
33
  fleet/_async/__init__.py,sha256=Wi8Tjj-Lfnxi4cPOkAxh2lynnpEBNni6mI6Iq80uOro,8054
34
34
  fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
35
- fleet/_async/client.py,sha256=DWI0nUpR3eD_onAbmiPvKemcqZ7adqLJTt0qXKBOMn4,32505
35
+ fleet/_async/client.py,sha256=Uu9o8LpUce8vOoFxqG0RDe4VtJpu1BJGIFbVecuO71g,32947
36
36
  fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
37
37
  fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
38
- fleet/_async/models.py,sha256=GX-sRciZDenW2O7Qx9w_ftOkJyE4ph1-92WMq6lynHE,12856
39
- fleet/_async/tasks.py,sha256=VFP00oAeyGvXkVOutLmrvKuM77BGLAc9ZoCH_Z2nXmg,16463
38
+ fleet/_async/models.py,sha256=6x3IPYuWz1v6zWjujqgzK2CpR2HB5Rme4LQFLyvUDXE,13164
39
+ fleet/_async/tasks.py,sha256=LiDDIrmT9mIqlUna-I903n4yVpLxbmxUSv4lPdaBcDE,16942
40
40
  fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  fleet/_async/env/client.py,sha256=C5WG5Ir_McXaFPZNdkQjj0w4V7xMIcw3QyVP5g-3kVk,1237
42
42
  fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
@@ -69,10 +69,12 @@ fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,
69
69
  fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
70
70
  fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
71
71
  fleet/verifiers/verifier.py,sha256=_lcxXVm8e0xRrK2gNJy9up7pW1zOkPRY5n5lQ85S8jg,14197
72
- fleet_python-0.2.60.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ fleet_python-0.2.62.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
73
73
  scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
74
74
  scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
75
- fleet_python-0.2.60.dist-info/METADATA,sha256=V0DNVpqY8l-Q0Gq8xfOPVZGxUbRaYfmeRvU3NBkwnaY,3304
76
- fleet_python-0.2.60.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
- fleet_python-0.2.60.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
78
- fleet_python-0.2.60.dist-info/RECORD,,
75
+ tests/__init__.py,sha256=Re1SdyxH8NfyL1kjhi7SQkGP1mYeWB-D6UALqdIMd8I,35
76
+ tests/test_verifier_from_string.py,sha256=Lxi3TpFHFb-hG4-UhLKZJkqo84ax9YJY8G6beO-1erM,13581
77
+ fleet_python-0.2.62.dist-info/METADATA,sha256=pkly98qTBJVHA9jkevvz_v3UKZyGa2-_RFVlbVGLlSg,3304
78
+ fleet_python-0.2.62.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ fleet_python-0.2.62.dist-info/top_level.txt,sha256=qb1zIbtEktyhRFZdqVytwg54l64qtoZL0wjHB4bUg3c,29
80
+ fleet_python-0.2.62.dist-info/RECORD,,
@@ -1,3 +1,4 @@
1
1
  examples
2
2
  fleet
3
3
  scripts
4
+ tests
tests/__init__.py ADDED
@@ -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"