fleet-python 0.2.53__py3-none-any.whl → 0.2.55__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.

@@ -0,0 +1,76 @@
1
+ import argparse
2
+ import json
3
+ import fleet
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(description="Export tasks to a JSON file")
11
+ parser.add_argument(
12
+ "--project-key",
13
+ "-p",
14
+ help="Optional project key to filter tasks",
15
+ default=None,
16
+ )
17
+ parser.add_argument(
18
+ "--output",
19
+ "-o",
20
+ help="Output JSON filename (defaults to {team_id}.json)",
21
+ default=None,
22
+ )
23
+
24
+ args = parser.parse_args()
25
+
26
+ # Get account info
27
+ account = fleet.env.account()
28
+ print(f"Exporting from team: {account.team_name}")
29
+
30
+ # Load tasks
31
+ if args.project_key:
32
+ print(f"Loading tasks from project: {args.project_key}")
33
+ tasks = fleet.load_tasks(project_key=args.project_key)
34
+ else:
35
+ print("Loading all tasks")
36
+ tasks = fleet.load_tasks()
37
+
38
+ print(f"\nFound {len(tasks)} task(s)")
39
+
40
+ # Validate that all tasks have verifier_func
41
+ print("Validating tasks have verifier_func...")
42
+ missing_verifier = []
43
+ for task in tasks:
44
+ if not task.verifier_func:
45
+ missing_verifier.append(task.key)
46
+
47
+ if missing_verifier:
48
+ print(f"\n✗ Error: {len(missing_verifier)} task(s) missing verifier_func:")
49
+ for key in missing_verifier[:10]: # Show first 10
50
+ print(f" - {key}")
51
+ if len(missing_verifier) > 10:
52
+ print(f" ... and {len(missing_verifier) - 10} more")
53
+ raise ValueError(
54
+ "All tasks must have a verifier_func. Cannot export tasks without verifiers."
55
+ )
56
+
57
+ print("✓ All tasks have verifier_func")
58
+
59
+ # Determine output filename
60
+ output_file = args.output or f"{account.team_id}.json"
61
+
62
+ # Export to JSON
63
+ print(f"\nExporting to: {output_file}")
64
+ with open(output_file, "w", encoding="utf-8") as f:
65
+ json.dump(
66
+ [task.model_dump() for task in tasks],
67
+ f,
68
+ indent=2,
69
+ ensure_ascii=False,
70
+ )
71
+
72
+ print(f"✓ Successfully exported {len(tasks)} task(s) to {output_file}")
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1,102 @@
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())
fleet/__init__.py CHANGED
@@ -51,10 +51,17 @@ from ._async.tasks import (
51
51
  Task,
52
52
  load_tasks as load_tasks_async,
53
53
  load_tasks_from_file as load_tasks_from_file_async,
54
+ import_task as import_task_async,
55
+ import_tasks as import_tasks_async,
54
56
  )
55
57
 
56
- # Import sync load_tasks function
57
- from .tasks import load_tasks, load_tasks_from_file
58
+ # Import sync task functions
59
+ from .tasks import (
60
+ load_tasks,
61
+ load_tasks_from_file,
62
+ import_task,
63
+ import_tasks,
64
+ )
58
65
 
59
66
  # Import shared types
60
67
  from .types import VerifierFunction
@@ -104,6 +111,12 @@ __all__ = [
104
111
  # Module-level functions (async is default)
105
112
  "load_tasks",
106
113
  "load_tasks_async",
114
+ "load_tasks_from_file",
115
+ "load_tasks_from_file_async",
116
+ "import_task",
117
+ "import_task_async",
118
+ "import_tasks",
119
+ "import_tasks_async",
107
120
  # Version
108
121
  "__version__",
109
122
  ]
fleet/_async/__init__.py CHANGED
@@ -86,6 +86,7 @@ __all__ = [
86
86
  "load_task_from_string",
87
87
  "load_task_from_json",
88
88
  "export_tasks",
89
+ "import_task",
89
90
  "import_tasks",
90
91
  "account",
91
92
  # Version
@@ -193,6 +194,21 @@ async def export_tasks(
193
194
  return await _async_global_client.get_client().export_tasks(env_key, filename)
194
195
 
195
196
 
197
+ async def import_task(task, project_key: Optional[str] = None):
198
+ """Import a single task.
199
+
200
+ Args:
201
+ task: Task object to import
202
+ project_key: Optional project key to associate with the task
203
+
204
+ Example:
205
+ task = fleet.Task(key="my-task", prompt="Do something", env_id="my-env")
206
+ await fleet.import_task(task)
207
+ await fleet.import_task(task, project_key="my-project")
208
+ """
209
+ return await _async_global_client.get_client().import_single_task(task, project_key)
210
+
211
+
196
212
  async def import_tasks(filename: str, project_key: Optional[str] = None):
197
213
  """Import tasks from a JSON file.
198
214
 
fleet/_async/client.py CHANGED
@@ -328,7 +328,9 @@ class AsyncFleet:
328
328
  task_json = json.loads(task_string)
329
329
  return await self.load_task_from_json(task_json)
330
330
 
331
- async def load_task_from_json(self, task_json: Dict) -> Task:
331
+ async def load_task_from_json(
332
+ self, task_json: Dict, raise_on_verifier_error: bool = False
333
+ ) -> Task:
332
334
  verifier = None
333
335
  verifier_code = task_json.get("verifier_func") or task_json.get("verifier_code")
334
336
 
@@ -356,9 +358,11 @@ class AsyncFleet:
356
358
  verifier_sha=task_json.get("verifier_sha", ""),
357
359
  )
358
360
  except Exception as e:
359
- logger.warning(
360
- f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
361
- )
361
+ error_msg = f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
362
+ if raise_on_verifier_error:
363
+ raise ValueError(error_msg) from e
364
+ else:
365
+ logger.warning(error_msg)
362
366
 
363
367
  task = Task(
364
368
  key=task_json.get("key", task_json.get("id")),
@@ -368,6 +372,8 @@ class AsyncFleet:
368
372
  ), # Use env_id or fallback to env_key
369
373
  created_at=task_json.get("created_at"),
370
374
  version=task_json.get("version"),
375
+ data_id=task_json.get("data_id"),
376
+ data_version=task_json.get("data_version"),
371
377
  env_variables=task_json.get("env_variables", {}),
372
378
  verifier_func=verifier_code, # Set verifier code
373
379
  verifier=verifier, # Use created verifier or None
@@ -382,6 +388,8 @@ class AsyncFleet:
382
388
  version: Optional[str] = None,
383
389
  team_id: Optional[str] = None,
384
390
  project_key: Optional[str] = None,
391
+ data_id: Optional[str] = None,
392
+ data_version: Optional[str] = None,
385
393
  ) -> List[Task]:
386
394
  """Load tasks for the authenticated team, with optional filtering.
387
395
 
@@ -390,6 +398,9 @@ class AsyncFleet:
390
398
  keys: Optional list of task keys to filter by
391
399
  version: Optional version to filter tasks by (client-side filter)
392
400
  team_id: Optional team_id to filter by (admin only)
401
+ project_key: Optional project key to filter tasks by
402
+ data_id: Optional data identifier to filter tasks by
403
+ data_version: Optional data version to filter tasks by
393
404
 
394
405
  Returns:
395
406
  List[Task] containing Task objects
@@ -403,6 +414,10 @@ class AsyncFleet:
403
414
  params["team_id"] = team_id
404
415
  if project_key is not None:
405
416
  params["project_key"] = project_key
417
+ if data_id is not None:
418
+ params["data_id"] = data_id
419
+ if data_version is not None:
420
+ params["data_version"] = data_version
406
421
 
407
422
  response = await self.client.request("GET", "/v1/tasks", params=params)
408
423
  task_list_response = TaskListResponse(**response.json())
@@ -507,6 +522,8 @@ class AsyncFleet:
507
522
  env_id=task_response.environment_id, # Map environment_id -> env_id
508
523
  created_at=task_response.created_at,
509
524
  version=task_response.version,
525
+ data_id=getattr(task_response, "data_id", None), # Get data_id if available
526
+ data_version=getattr(task_response, "data_version", None), # Get data_version if available
510
527
  env_variables=task_response.env_variables or {},
511
528
  verifier_func=verifier_func, # Set verifier code
512
529
  verifier=verifier, # Use created verifier or None
@@ -517,6 +534,14 @@ class AsyncFleet:
517
534
  # Apply client-side filtering for version if specified
518
535
  if version is not None:
519
536
  tasks = [task for task in tasks if task.version == version]
537
+
538
+ # Apply client-side filtering for data_id if specified
539
+ if data_id is not None:
540
+ tasks = [task for task in tasks if task.data_id == data_id]
541
+
542
+ # Apply client-side filtering for data_version if specified
543
+ if data_version is not None:
544
+ tasks = [task for task in tasks if task.data_version == data_version]
520
545
 
521
546
  return tasks
522
547
 
@@ -571,6 +596,13 @@ class AsyncFleet:
571
596
  Response from the API, or None if the import failed
572
597
  """
573
598
  try:
599
+ # Validate that verifier_func exists
600
+ if not task.verifier_func:
601
+ raise ValueError(
602
+ f"Task {task.key} is missing verifier_func. "
603
+ "All tasks must have a verifier_func to be imported."
604
+ )
605
+
574
606
  params = {}
575
607
  if project_key:
576
608
  params["project_key"] = project_key
@@ -591,14 +623,33 @@ class AsyncFleet:
591
623
 
592
624
  Returns:
593
625
  List[Task] containing imported Task objects
626
+
627
+ Raises:
628
+ ValueError: If any task is missing verifier_func or has invalid verifier code
594
629
  """
595
630
  with open(filename, "r", encoding="utf-8") as f:
596
631
  tasks_data = json.load(f)
597
632
 
598
- # Create tasks from the loaded data
633
+ # Create tasks from the loaded data using load_task_from_json
634
+ # This will validate and create verifiers properly
599
635
  tasks = []
600
636
  for task_data in tasks_data:
601
- task = Task(**task_data)
637
+ # Validate that verifier_func exists
638
+ verifier_code = task_data.get("verifier_func") or task_data.get(
639
+ "verifier_code"
640
+ )
641
+ if not verifier_code:
642
+ task_key = task_data.get("key", task_data.get("id", "unknown"))
643
+ raise ValueError(
644
+ f"Task {task_key} is missing verifier_func. "
645
+ "All tasks must have a verifier_func to be imported."
646
+ )
647
+
648
+ # Use load_task_from_json to properly create and validate the task
649
+ # Pass raise_on_verifier_error=True to fail fast on invalid verifier code
650
+ task = await self.load_task_from_json(
651
+ task_data, raise_on_verifier_error=True
652
+ )
602
653
  tasks.append(task)
603
654
 
604
655
  # Use semaphore to limit concurrency to 20
fleet/_async/tasks.py CHANGED
@@ -25,6 +25,8 @@ class Task(BaseModel):
25
25
  )
26
26
  created_at: Optional[datetime] = Field(None, description="Task creation timestamp")
27
27
  version: Optional[str] = Field(None, description="Task version")
28
+ data_id: Optional[str] = Field(None, description="Data identifier")
29
+ data_version: Optional[str] = Field(None, description="Data version")
28
30
  verifier_func: Optional[str] = Field(None, description="Verifier function code")
29
31
  verifier: Optional[Any] = Field(
30
32
  None,
@@ -58,6 +60,15 @@ class Task(BaseModel):
58
60
  return f"{self.env_id}:{self.version}"
59
61
  return self.env_id
60
62
 
63
+ @property
64
+ def data_key(self) -> Optional[str]:
65
+ """Get the data key combining data_id and data_version."""
66
+ if self.data_id and self.data_version:
67
+ return f"{self.data_id}:{self.data_version}"
68
+ elif self.data_id:
69
+ return self.data_id
70
+ return None
71
+
61
72
  class Config:
62
73
  """Pydantic model configuration."""
63
74
 
@@ -282,6 +293,8 @@ async def load_tasks(
282
293
  version: Optional[str] = None,
283
294
  team_id: Optional[str] = None,
284
295
  project_key: Optional[str] = None,
296
+ data_id: Optional[str] = None,
297
+ data_version: Optional[str] = None,
285
298
  ) -> List[Task]:
286
299
  """Convenience function to load tasks with optional filtering.
287
300
 
@@ -290,11 +303,15 @@ async def load_tasks(
290
303
  keys: Optional list of task keys to filter by
291
304
  version: Optional version to filter tasks by
292
305
  team_id: Optional team_id to filter by (admin only)
306
+ project_key: Optional project key to filter tasks by
307
+ data_id: Optional data identifier to filter tasks by
308
+ data_version: Optional data version to filter tasks by
293
309
 
294
310
  Examples:
295
311
  tasks = await fleet.load_tasks(env_key="fira")
296
312
  tasks = await fleet.load_tasks(keys=["task1", "task2"])
297
313
  tasks = await fleet.load_tasks(env_key="fira", version="v1.0")
314
+ tasks = await fleet.load_tasks(data_id="my-data", data_version="v1.0")
298
315
  """
299
316
  # Use the global client by default so users can pre-configure it once
300
317
  from .global_client import get_client
@@ -306,6 +323,8 @@ async def load_tasks(
306
323
  version=version,
307
324
  team_id=team_id,
308
325
  project_key=project_key,
326
+ data_id=data_id,
327
+ data_version=data_version,
309
328
  )
310
329
 
311
330
 
@@ -332,3 +351,44 @@ async def update_task(
332
351
  return await client.update_task(
333
352
  task_key=task_key, prompt=prompt, verifier_code=verifier_code
334
353
  )
354
+
355
+
356
+ async def import_task(task: Task, project_key: Optional[str] = None):
357
+ """Convenience function to import a single task.
358
+
359
+ Args:
360
+ task: Task object to import
361
+ project_key: Optional project key to associate with the task
362
+
363
+ Returns:
364
+ Response from the API, or None if the import failed
365
+
366
+ Examples:
367
+ task = fleet.Task(key="my-task", prompt="Do something", env_id="my-env")
368
+ response = await fleet.import_task(task)
369
+ response = await fleet.import_task(task, project_key="my-project")
370
+ """
371
+ from .global_client import get_client
372
+
373
+ client = get_client()
374
+ return await client.import_single_task(task, project_key=project_key)
375
+
376
+
377
+ async def import_tasks(filename: str, project_key: Optional[str] = None):
378
+ """Convenience function to import tasks from a JSON file.
379
+
380
+ Args:
381
+ filename: Path to the JSON file of Task objects to import
382
+ project_key: Optional project key to associate with the tasks
383
+
384
+ Returns:
385
+ List of responses from the API for successfully imported tasks
386
+
387
+ Examples:
388
+ responses = await fleet.import_tasks("tasks.json")
389
+ responses = await fleet.import_tasks("tasks.json", project_key="my-project")
390
+ """
391
+ from .global_client import get_client
392
+
393
+ client = get_client()
394
+ return await client.import_tasks(filename, project_key=project_key)
fleet/client.py CHANGED
@@ -326,7 +326,9 @@ class Fleet:
326
326
  task_json = json.loads(task_string)
327
327
  return self.load_task_from_json(task_json)
328
328
 
329
- def load_task_from_json(self, task_json: Dict) -> Task:
329
+ def load_task_from_json(
330
+ self, task_json: Dict, raise_on_verifier_error: bool = False
331
+ ) -> Task:
330
332
  verifier = None
331
333
  verifier_code = task_json.get("verifier_func") or task_json.get("verifier_code")
332
334
 
@@ -354,9 +356,11 @@ class Fleet:
354
356
  verifier_sha=task_json.get("verifier_sha", ""),
355
357
  )
356
358
  except Exception as e:
357
- logger.warning(
358
- f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
359
- )
359
+ error_msg = f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
360
+ if raise_on_verifier_error:
361
+ raise ValueError(error_msg) from e
362
+ else:
363
+ logger.warning(error_msg)
360
364
 
361
365
  task = Task(
362
366
  key=task_json.get("key", task_json.get("id")),
@@ -366,6 +370,8 @@ class Fleet:
366
370
  ), # Use env_id or fallback to env_key
367
371
  created_at=task_json.get("created_at"),
368
372
  version=task_json.get("version"),
373
+ data_id=task_json.get("data_id"),
374
+ data_version=task_json.get("data_version"),
369
375
  env_variables=task_json.get("env_variables", {}),
370
376
  verifier_func=verifier_code, # Set verifier code
371
377
  verifier=verifier, # Use created verifier or None
@@ -380,6 +386,8 @@ class Fleet:
380
386
  version: Optional[str] = None,
381
387
  team_id: Optional[str] = None,
382
388
  project_key: Optional[str] = None,
389
+ data_id: Optional[str] = None,
390
+ data_version: Optional[str] = None,
383
391
  ) -> List[Task]:
384
392
  """Load tasks for the authenticated team, with optional filtering.
385
393
 
@@ -388,6 +396,9 @@ class Fleet:
388
396
  keys: Optional list of task keys to filter by
389
397
  version: Optional version to filter tasks by (client-side filter)
390
398
  team_id: Optional team_id to filter by (admin only)
399
+ project_key: Optional project key to filter tasks by
400
+ data_id: Optional data identifier to filter tasks by
401
+ data_version: Optional data version to filter tasks by
391
402
 
392
403
  Returns:
393
404
  List[Task] containing Task objects
@@ -401,6 +412,10 @@ class Fleet:
401
412
  params["team_id"] = team_id
402
413
  if project_key is not None:
403
414
  params["project_key"] = project_key
415
+ if data_id is not None:
416
+ params["data_id"] = data_id
417
+ if data_version is not None:
418
+ params["data_version"] = data_version
404
419
 
405
420
  response = self.client.request("GET", "/v1/tasks", params=params)
406
421
  task_list_response = TaskListResponse(**response.json())
@@ -513,6 +528,8 @@ class Fleet:
513
528
  env_id=task_response.environment_id, # Map environment_id -> env_id
514
529
  created_at=task_response.created_at,
515
530
  version=task_response.version,
531
+ data_id=getattr(task_response, "data_id", None), # Get data_id if available
532
+ data_version=getattr(task_response, "data_version", None), # Get data_version if available
516
533
  env_variables=task_response.env_variables or {},
517
534
  verifier_func=verifier_func, # Set verifier code
518
535
  verifier=verifier, # Use created verifier or None
@@ -523,6 +540,14 @@ class Fleet:
523
540
  # Apply client-side filtering for version if specified
524
541
  if version is not None:
525
542
  tasks = [task for task in tasks if task.version == version]
543
+
544
+ # Apply client-side filtering for data_id if specified
545
+ if data_id is not None:
546
+ tasks = [task for task in tasks if task.data_id == data_id]
547
+
548
+ # Apply client-side filtering for data_version if specified
549
+ if data_version is not None:
550
+ tasks = [task for task in tasks if task.data_version == data_version]
526
551
 
527
552
  return tasks
528
553
 
@@ -577,10 +602,19 @@ class Fleet:
577
602
  Response from the API, or None if the import failed
578
603
  """
579
604
  try:
605
+ # Validate that verifier_func exists
606
+ if not task.verifier_func:
607
+ raise ValueError(
608
+ f"Task {task.key} is missing verifier_func. "
609
+ "All tasks must have a verifier_func to be imported."
610
+ )
611
+
580
612
  params = {}
581
613
  if project_key:
582
614
  params["project_key"] = project_key
583
- response = self.client.request("POST", "/v1/tasks", json=task.model_dump(), params=params)
615
+ response = self.client.request(
616
+ "POST", "/v1/tasks", json=task.model_dump(), params=params
617
+ )
584
618
  return response
585
619
  except Exception as e:
586
620
  logger.error(f"Failed to import task {task.key}: {e}")
@@ -595,19 +629,38 @@ class Fleet:
595
629
 
596
630
  Returns:
597
631
  List[Task] containing imported Task objects
632
+
633
+ Raises:
634
+ ValueError: If any task is missing verifier_func or has invalid verifier code
598
635
  """
599
636
  with open(filename, "r", encoding="utf-8") as f:
600
637
  tasks_data = json.load(f)
601
638
 
602
- # Create tasks from the loaded data
639
+ # Create tasks from the loaded data using load_task_from_json
640
+ # This will validate and create verifiers properly
603
641
  tasks = []
604
642
  for task_data in tasks_data:
605
- task = Task(**task_data)
643
+ # Validate that verifier_func exists
644
+ verifier_code = task_data.get("verifier_func") or task_data.get(
645
+ "verifier_code"
646
+ )
647
+ if not verifier_code:
648
+ task_key = task_data.get("key", task_data.get("id", "unknown"))
649
+ raise ValueError(
650
+ f"Task {task_key} is missing verifier_func. "
651
+ "All tasks must have a verifier_func to be imported."
652
+ )
653
+
654
+ # Use load_task_from_json to properly create and validate the task
655
+ # Pass raise_on_verifier_error=True to fail fast on invalid verifier code
656
+ task = self.load_task_from_json(task_data, raise_on_verifier_error=True)
606
657
  tasks.append(task)
607
658
 
608
659
  # Use ThreadPoolExecutor to parallelize the imports with max 20 workers
609
660
  with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
610
- responses = list(executor.map(lambda t: self.import_single_task(t, project_key), tasks))
661
+ responses = list(
662
+ executor.map(lambda t: self.import_single_task(t, project_key), tasks)
663
+ )
611
664
 
612
665
  # Filter out None values (failed imports)
613
666
  return [r for r in responses if r is not None]
fleet/tasks.py CHANGED
@@ -26,6 +26,8 @@ class Task(BaseModel):
26
26
  )
27
27
  created_at: Optional[datetime] = Field(None, description="Task creation timestamp")
28
28
  version: Optional[str] = Field(None, description="Task version")
29
+ data_id: Optional[str] = Field(None, description="Data identifier")
30
+ data_version: Optional[str] = Field(None, description="Data version")
29
31
  verifier_func: Optional[str] = Field(None, description="Verifier function code")
30
32
  verifier: Optional[Any] = Field(
31
33
  None,
@@ -59,6 +61,15 @@ class Task(BaseModel):
59
61
  return f"{self.env_id}:{self.version}"
60
62
  return self.env_id
61
63
 
64
+ @property
65
+ def data_key(self) -> Optional[str]:
66
+ """Get the data key combining data_id and data_version."""
67
+ if self.data_id and self.data_version:
68
+ return f"{self.data_id}:{self.data_version}"
69
+ elif self.data_id:
70
+ return self.data_id
71
+ return None
72
+
62
73
  class Config:
63
74
  """Pydantic model configuration."""
64
75
 
@@ -285,6 +296,8 @@ def load_tasks(
285
296
  version: Optional[str] = None,
286
297
  team_id: Optional[str] = None,
287
298
  project_key: Optional[str] = None,
299
+ data_id: Optional[str] = None,
300
+ data_version: Optional[str] = None,
288
301
  ) -> List[Task]:
289
302
  """Convenience function to load tasks with optional filtering.
290
303
 
@@ -293,11 +306,15 @@ def load_tasks(
293
306
  keys: Optional list of task keys to filter by
294
307
  version: Optional version to filter tasks by
295
308
  team_id: Optional team_id to filter by (admin only)
309
+ project_key: Optional project key to filter tasks by
310
+ data_id: Optional data identifier to filter tasks by
311
+ data_version: Optional data version to filter tasks by
296
312
 
297
313
  Examples:
298
- tasks = await fleet.load_tasks(env_key="fira")
299
- tasks = await fleet.load_tasks(keys=["task1", "task2"])
300
- tasks = await fleet.load_tasks(env_key="fira", version="v1.0")
314
+ tasks = fleet.load_tasks(env_key="fira")
315
+ tasks = fleet.load_tasks(keys=["task1", "task2"])
316
+ tasks = fleet.load_tasks(env_key="fira", version="v1.0")
317
+ tasks = fleet.load_tasks(data_id="my-data", data_version="v1.0")
301
318
  """
302
319
  # Use the global client by default so users can pre-configure it once
303
320
  from .global_client import get_client
@@ -309,6 +326,8 @@ def load_tasks(
309
326
  version=version,
310
327
  team_id=team_id,
311
328
  project_key=project_key,
329
+ data_id=data_id,
330
+ data_version=data_version,
312
331
  )
313
332
 
314
333
 
@@ -335,3 +354,44 @@ def update_task(
335
354
  return client.update_task(
336
355
  task_key=task_key, prompt=prompt, verifier_code=verifier_code
337
356
  )
357
+
358
+
359
+ def import_task(task: Task, project_key: Optional[str] = None):
360
+ """Convenience function to import a single task.
361
+
362
+ Args:
363
+ task: Task object to import
364
+ project_key: Optional project key to associate with the task
365
+
366
+ Returns:
367
+ Response from the API, or None if the import failed
368
+
369
+ Examples:
370
+ task = fleet.Task(key="my-task", prompt="Do something", env_id="my-env")
371
+ response = fleet.import_task(task)
372
+ response = fleet.import_task(task, project_key="my-project")
373
+ """
374
+ from .global_client import get_client
375
+
376
+ client = get_client()
377
+ return client.import_single_task(task, project_key=project_key)
378
+
379
+
380
+ def import_tasks(filename: str, project_key: Optional[str] = None):
381
+ """Convenience function to import tasks from a JSON file.
382
+
383
+ Args:
384
+ filename: Path to the JSON file of Task objects to import
385
+ project_key: Optional project key to associate with the tasks
386
+
387
+ Returns:
388
+ List of responses from the API for successfully imported tasks
389
+
390
+ Examples:
391
+ responses = fleet.import_tasks("tasks.json")
392
+ responses = fleet.import_tasks("tasks.json", project_key="my-project")
393
+ """
394
+ from .global_client import get_client
395
+
396
+ client = get_client()
397
+ return client.import_tasks(filename, project_key=project_key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.53
3
+ Version: 0.2.55
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,9 @@ 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=BSRmsGIIk34QYanB8rPlaFbmn3MBxaf7TyFVrbsdPeU,2114
14
15
  examples/gemini_example.py,sha256=qj9WDazQTYNiRHNeUg9Tjkp33lJMwbx8gDfpFe1sDQo,16180
16
+ examples/import_tasks.py,sha256=pb60dOXnOn-G1INQxV1wujeHmmY2oWz9Ddj9SbMm1pk,3139
15
17
  examples/json_tasks_example.py,sha256=CYPESGGtOo0fmsDdLidujTfsE4QlJHw7rOhyVqPJ_Ls,5329
16
18
  examples/nova_act_example.py,sha256=rH23Lp74Okf0rn8ynMdWjK2aviEf5NLPH4k_53Pyxho,831
17
19
  examples/openai_example.py,sha256=dEWERrTEP5xBiGkLkQjBQGd2NqoxX6gcW6XteBPsWFQ,8231
@@ -19,22 +21,22 @@ examples/openai_simple_example.py,sha256=HmiufucrAZne7tHq9uoEsDWlEhjNC265bQAyIGB
19
21
  examples/query_builder_example.py,sha256=-cOMfWGNifYfYEt_Ds73XpwATZvFDL6F4KTkVxdMjzg,3951
20
22
  examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
21
23
  examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
22
- fleet/__init__.py,sha256=Mkdeh45N47lnSv73Eehj92cGU-AImUitvDWJLFhEp0Y,3844
24
+ fleet/__init__.py,sha256=fwIcuaJPPdLMdKPsazfz4EAPeJchl9MBN4nxs0YlOjw,4117
23
25
  fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
24
- fleet/client.py,sha256=zu05ApXhJrKX7UiMF8TOY1yNWiHRGlYRAMBmhaCsOSo,27806
26
+ fleet/client.py,sha256=0RwFXBdiBSK7ziTrUe2uzhHX14MxuboIQhMCoRMC300,30315
25
27
  fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
26
28
  fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
27
29
  fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
28
30
  fleet/models.py,sha256=d9eish0KO3t4VCNu8h8Q_6K1Xj-crYo5Fejtle0Ur28,13056
29
- fleet/tasks.py,sha256=uR94wq7SG4ymJYLq24F2iYn0el58C2Czb2oK-8rIs8s,12344
31
+ fleet/tasks.py,sha256=nNGyeGgWvluzSatd_AHqj9nk8jJ5dgCySPODCaySJ8M,14487
30
32
  fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
31
- fleet/_async/__init__.py,sha256=x2esWn8yf2AMfzHRmSHtFkXcxbrSSXCZdJyrFnJldEo,7033
33
+ fleet/_async/__init__.py,sha256=ruGtdMl9BRQRsJygNszPT89ZbMy6AYv1mmeYoB--r60,7557
32
34
  fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
33
- fleet/_async/client.py,sha256=TZJsEg43IiYPnSA3S48GRpwAYg1fehcHVbYHA3yUHXY,28266
35
+ fleet/_async/client.py,sha256=uiYufTUAG3o_nseflp_DxW8yLe8JnprIxCwjZ4CsqoI,30751
34
36
  fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
35
37
  fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
36
38
  fleet/_async/models.py,sha256=GX-sRciZDenW2O7Qx9w_ftOkJyE4ph1-92WMq6lynHE,12856
37
- fleet/_async/tasks.py,sha256=hrtvKlqepQOFnlJv6h5S06UWNWtn2hQlgSp3eYj5m30,12226
39
+ fleet/_async/tasks.py,sha256=6EzoIuU-LuL-m716_ACV4oO3fNO_3Y1mAzrQ6MZDdwc,14441
38
40
  fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
41
  fleet/_async/env/client.py,sha256=C5WG5Ir_McXaFPZNdkQjj0w4V7xMIcw3QyVP5g-3kVk,1237
40
42
  fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
@@ -67,10 +69,10 @@ fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,
67
69
  fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
68
70
  fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
69
71
  fleet/verifiers/verifier.py,sha256=_lcxXVm8e0xRrK2gNJy9up7pW1zOkPRY5n5lQ85S8jg,14197
70
- fleet_python-0.2.53.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
72
+ fleet_python-0.2.55.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
71
73
  scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
72
74
  scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
73
- fleet_python-0.2.53.dist-info/METADATA,sha256=SaC_PN_pQ1GG1g3RdaitFUy0PJWpD-1-hCIOT4sqcn8,3304
74
- fleet_python-0.2.53.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
- fleet_python-0.2.53.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
76
- fleet_python-0.2.53.dist-info/RECORD,,
75
+ fleet_python-0.2.55.dist-info/METADATA,sha256=XOtlVrz0SxTxmvmv_EMP4UOhd-T7RJzXaHtPC9vCtzs,3304
76
+ fleet_python-0.2.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ fleet_python-0.2.55.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
78
+ fleet_python-0.2.55.dist-info/RECORD,,