fleet-python 0.2.60__tar.gz → 0.2.62__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.

Files changed (85) hide show
  1. {fleet_python-0.2.60 → fleet_python-0.2.62}/PKG-INFO +1 -1
  2. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/export_tasks.py +19 -2
  3. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/client.py +6 -0
  4. {fleet_python-0.2.60/fleet → fleet_python-0.2.62/fleet/_async}/models.py +5 -4
  5. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/tasks.py +10 -2
  6. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/client.py +6 -0
  7. {fleet_python-0.2.60/fleet/_async → fleet_python-0.2.62/fleet}/models.py +12 -1
  8. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/tasks.py +10 -2
  9. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet_python.egg-info/PKG-INFO +1 -1
  10. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet_python.egg-info/SOURCES.txt +3 -1
  11. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet_python.egg-info/top_level.txt +1 -0
  12. {fleet_python-0.2.60 → fleet_python-0.2.62}/pyproject.toml +1 -1
  13. fleet_python-0.2.62/tests/__init__.py +1 -0
  14. fleet_python-0.2.62/tests/test_verifier_from_string.py +420 -0
  15. {fleet_python-0.2.60 → fleet_python-0.2.62}/LICENSE +0 -0
  16. {fleet_python-0.2.60 → fleet_python-0.2.62}/README.md +0 -0
  17. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/diff_example.py +0 -0
  18. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/dsl_example.py +0 -0
  19. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example.py +0 -0
  20. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/exampleResume.py +0 -0
  21. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_account.py +0 -0
  22. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_action_log.py +0 -0
  23. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_client.py +0 -0
  24. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_mcp_anthropic.py +0 -0
  25. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_mcp_openai.py +0 -0
  26. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_sync.py +0 -0
  27. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_task.py +0 -0
  28. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_tasks.py +0 -0
  29. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/example_verifier.py +0 -0
  30. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/gemini_example.py +0 -0
  31. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/import_tasks.py +0 -0
  32. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/json_tasks_example.py +0 -0
  33. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/nova_act_example.py +0 -0
  34. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/openai_example.py +0 -0
  35. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/openai_simple_example.py +0 -0
  36. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/query_builder_example.py +0 -0
  37. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/quickstart.py +0 -0
  38. {fleet_python-0.2.60 → fleet_python-0.2.62}/examples/test_cdp_logging.py +0 -0
  39. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/__init__.py +0 -0
  40. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/__init__.py +0 -0
  41. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/base.py +0 -0
  42. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/env/__init__.py +0 -0
  43. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/env/client.py +0 -0
  44. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/exceptions.py +0 -0
  45. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/global_client.py +0 -0
  46. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/instance/__init__.py +0 -0
  47. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/instance/base.py +0 -0
  48. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/instance/client.py +0 -0
  49. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/resources/__init__.py +0 -0
  50. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/resources/base.py +0 -0
  51. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/resources/browser.py +0 -0
  52. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/resources/mcp.py +0 -0
  53. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/resources/sqlite.py +0 -0
  54. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/verifiers/__init__.py +0 -0
  55. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/verifiers/bundler.py +0 -0
  56. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/_async/verifiers/verifier.py +0 -0
  57. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/base.py +0 -0
  58. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/config.py +0 -0
  59. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/env/__init__.py +0 -0
  60. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/env/client.py +0 -0
  61. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/exceptions.py +0 -0
  62. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/global_client.py +0 -0
  63. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/instance/__init__.py +0 -0
  64. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/instance/base.py +0 -0
  65. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/instance/client.py +0 -0
  66. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/instance/models.py +0 -0
  67. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/resources/__init__.py +0 -0
  68. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/resources/base.py +0 -0
  69. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/resources/browser.py +0 -0
  70. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/resources/mcp.py +0 -0
  71. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/resources/sqlite.py +0 -0
  72. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/types.py +0 -0
  73. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/__init__.py +0 -0
  74. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/bundler.py +0 -0
  75. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/code.py +0 -0
  76. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/db.py +0 -0
  77. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/decorator.py +0 -0
  78. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/parse.py +0 -0
  79. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/sql_differ.py +0 -0
  80. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet/verifiers/verifier.py +0 -0
  81. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet_python.egg-info/dependency_links.txt +0 -0
  82. {fleet_python-0.2.60 → fleet_python-0.2.62}/fleet_python.egg-info/requires.txt +0 -0
  83. {fleet_python-0.2.60 → fleet_python-0.2.62}/scripts/fix_sync_imports.py +0 -0
  84. {fleet_python-0.2.60 → fleet_python-0.2.62}/scripts/unasync.py +0 -0
  85. {fleet_python-0.2.60 → fleet_python-0.2.62}/setup.cfg +0 -0
@@ -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
@@ -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()
@@ -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
 
@@ -55,9 +55,7 @@ class Instance(BaseModel):
55
55
 
56
56
  class InstanceRequest(BaseModel):
57
57
  env_key: str = Field(..., title="Env Key")
58
- env_version: Optional[str] = Field(None, title="Env Version")
59
- data_key: Optional[str] = Field(None, title="Data Key")
60
- data_version: Optional[str] = Field(None, title="Data Version")
58
+ version: Optional[str] = Field(None, title="Version")
61
59
  region: Optional[str] = Field("us-west-1", title="Region")
62
60
  seed: Optional[int] = Field(None, title="Seed")
63
61
  timestamp: Optional[int] = Field(None, title="Timestamp")
@@ -67,7 +65,6 @@ class InstanceRequest(BaseModel):
67
65
  task_id: Optional[str] = Field(None, title="Task Id")
68
66
  force_pull: Optional[bool] = Field(None, title="Force Pull")
69
67
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
70
- image_type: Optional[str] = Field(None, title="Image Type")
71
68
  created_from: Optional[str] = Field(None, title="Created From")
72
69
 
73
70
 
@@ -157,6 +154,7 @@ class TaskRequest(BaseModel):
157
154
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
158
155
  version: Optional[str] = Field(None, title="Version")
159
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")
160
158
 
161
159
 
162
160
  class TaskUpdateRequest(BaseModel):
@@ -183,8 +181,11 @@ class TaskResponse(BaseModel):
183
181
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
184
182
  verifier_func: Optional[str] = Field(None, title="Verifier Func")
185
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")
186
186
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
187
187
  verifier: Optional[VerifierData] = Field(None, title="Verifier")
188
+ output_json_schema: Optional[Dict[str, Any]] = Field(None, title="Output Json Schema")
188
189
 
189
190
 
190
191
  class ValidationError(BaseModel):
@@ -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
  )
@@ -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
 
@@ -55,7 +55,9 @@ class Instance(BaseModel):
55
55
 
56
56
  class InstanceRequest(BaseModel):
57
57
  env_key: str = Field(..., title="Env Key")
58
- version: Optional[str] = Field(None, title="Version")
58
+ env_version: Optional[str] = Field(None, title="Env Version")
59
+ data_key: Optional[str] = Field(None, title="Data Key")
60
+ data_version: Optional[str] = Field(None, title="Data Version")
59
61
  region: Optional[str] = Field("us-west-1", title="Region")
60
62
  seed: Optional[int] = Field(None, title="Seed")
61
63
  timestamp: Optional[int] = Field(None, title="Timestamp")
@@ -65,6 +67,7 @@ class InstanceRequest(BaseModel):
65
67
  task_id: Optional[str] = Field(None, title="Task Id")
66
68
  force_pull: Optional[bool] = Field(None, title="Force Pull")
67
69
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
70
+ image_type: Optional[str] = Field(None, title="Image Type")
68
71
  created_from: Optional[str] = Field(None, title="Created From")
69
72
 
70
73
 
@@ -154,6 +157,9 @@ class TaskRequest(BaseModel):
154
157
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
155
158
  version: Optional[str] = Field(None, title="Version")
156
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
+ )
157
163
 
158
164
 
159
165
  class TaskUpdateRequest(BaseModel):
@@ -180,8 +186,13 @@ class TaskResponse(BaseModel):
180
186
  verifier_id: Optional[str] = Field(None, title="Verifier Id")
181
187
  verifier_func: Optional[str] = Field(None, title="Verifier Func")
182
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")
183
191
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
184
192
  verifier: Optional[VerifierData] = Field(None, title="Verifier")
193
+ output_json_schema: Optional[Dict[str, Any]] = Field(
194
+ None, title="Output Json Schema"
195
+ )
185
196
 
186
197
 
187
198
  class ValidationError(BaseModel):
@@ -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
@@ -78,4 +78,6 @@ fleet_python.egg-info/dependency_links.txt
78
78
  fleet_python.egg-info/requires.txt
79
79
  fleet_python.egg-info/top_level.txt
80
80
  scripts/fix_sync_imports.py
81
- scripts/unasync.py
81
+ scripts/unasync.py
82
+ tests/__init__.py
83
+ tests/test_verifier_from_string.py
@@ -2,3 +2,4 @@ dist
2
2
  examples
3
3
  fleet
4
4
  scripts
5
+ tests
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fleet-python"
7
- version = "0.2.60"
7
+ version = "0.2.62"
8
8
  description = "Python SDK for Fleet environments"
9
9
  authors = [
10
10
  {name = "Fleet AI", email = "nic@fleet.so"},
@@ -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"
File without changes
File without changes
File without changes