fleet-python 0.2.43__tar.gz → 0.2.44__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 (81) hide show
  1. {fleet_python-0.2.43 → fleet_python-0.2.44}/PKG-INFO +1 -1
  2. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/__init__.py +9 -7
  3. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/__init__.py +28 -16
  4. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/client.py +44 -16
  5. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/instance/client.py +1 -1
  6. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/resources/sqlite.py +34 -34
  7. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/tasks.py +13 -1
  8. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/verifiers/verifier.py +3 -3
  9. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/client.py +44 -13
  10. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/instance/client.py +2 -4
  11. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/resources/sqlite.py +37 -43
  12. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/tasks.py +22 -37
  13. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/__init__.py +1 -1
  14. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/db.py +41 -36
  15. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/parse.py +4 -1
  16. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/sql_differ.py +8 -8
  17. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/verifier.py +19 -7
  18. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet_python.egg-info/PKG-INFO +1 -1
  19. {fleet_python-0.2.43 → fleet_python-0.2.44}/pyproject.toml +1 -1
  20. {fleet_python-0.2.43 → fleet_python-0.2.44}/LICENSE +0 -0
  21. {fleet_python-0.2.43 → fleet_python-0.2.44}/README.md +0 -0
  22. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/diff_example.py +0 -0
  23. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/dsl_example.py +0 -0
  24. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example.py +0 -0
  25. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/exampleResume.py +0 -0
  26. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_account.py +0 -0
  27. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_action_log.py +0 -0
  28. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_client.py +0 -0
  29. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_mcp_anthropic.py +0 -0
  30. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_mcp_openai.py +0 -0
  31. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_sync.py +0 -0
  32. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_task.py +0 -0
  33. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_tasks.py +0 -0
  34. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/example_verifier.py +0 -0
  35. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/gemini_example.py +0 -0
  36. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/json_tasks_example.py +0 -0
  37. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/nova_act_example.py +0 -0
  38. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/openai_example.py +0 -0
  39. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/openai_simple_example.py +0 -0
  40. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/query_builder_example.py +0 -0
  41. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/quickstart.py +0 -0
  42. {fleet_python-0.2.43 → fleet_python-0.2.44}/examples/test_cdp_logging.py +0 -0
  43. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/base.py +0 -0
  44. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/env/__init__.py +0 -0
  45. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/env/client.py +0 -0
  46. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/exceptions.py +0 -0
  47. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/global_client.py +0 -0
  48. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/instance/__init__.py +0 -0
  49. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/instance/base.py +0 -0
  50. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/models.py +0 -0
  51. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/resources/__init__.py +0 -0
  52. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/resources/base.py +0 -0
  53. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/resources/browser.py +0 -0
  54. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/resources/mcp.py +0 -0
  55. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/verifiers/__init__.py +0 -0
  56. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/_async/verifiers/bundler.py +0 -0
  57. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/base.py +0 -0
  58. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/config.py +0 -0
  59. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/env/__init__.py +0 -0
  60. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/env/client.py +0 -0
  61. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/exceptions.py +0 -0
  62. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/global_client.py +0 -0
  63. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/instance/__init__.py +0 -0
  64. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/instance/base.py +0 -0
  65. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/instance/models.py +0 -0
  66. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/models.py +0 -0
  67. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/resources/__init__.py +0 -0
  68. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/resources/base.py +0 -0
  69. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/resources/browser.py +0 -0
  70. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/resources/mcp.py +0 -0
  71. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/types.py +0 -0
  72. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/bundler.py +0 -0
  73. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/code.py +0 -0
  74. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet/verifiers/decorator.py +0 -0
  75. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet_python.egg-info/SOURCES.txt +0 -0
  76. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet_python.egg-info/dependency_links.txt +0 -0
  77. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet_python.egg-info/requires.txt +0 -0
  78. {fleet_python-0.2.43 → fleet_python-0.2.44}/fleet_python.egg-info/top_level.txt +0 -0
  79. {fleet_python-0.2.43 → fleet_python-0.2.44}/scripts/fix_sync_imports.py +0 -0
  80. {fleet_python-0.2.43 → fleet_python-0.2.44}/scripts/unasync.py +0 -0
  81. {fleet_python-0.2.43 → fleet_python-0.2.44}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.43
3
+ Version: 0.2.44
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -46,11 +46,15 @@ from ._async.verifiers import (
46
46
  AsyncVerifierFunction,
47
47
  )
48
48
 
49
- # Import async tasks (default tasks are async for modern usage)
50
- from ._async.tasks import Task, load_tasks as load_tasks_async
49
+ # Import async tasks (default tasks are async for modern usage)
50
+ from ._async.tasks import (
51
+ Task,
52
+ load_tasks as load_tasks_async,
53
+ load_tasks_from_file as load_tasks_from_file_async,
54
+ )
51
55
 
52
56
  # Import sync load_tasks function
53
- from .tasks import load_tasks
57
+ from .tasks import load_tasks, load_tasks_from_file
54
58
 
55
59
  # Import shared types
56
60
  from .types import VerifierFunction
@@ -105,13 +109,11 @@ __all__ = [
105
109
  ]
106
110
 
107
111
 
108
-
109
-
110
112
  def configure(
111
113
  api_key: Optional[str] = None,
112
114
  base_url: Optional[str] = None,
113
- max_retries: int | None = None,
114
- timeout: float | None = None,
115
+ max_retries: Optional[int] = None,
116
+ timeout: Optional[float] = None,
115
117
  ):
116
118
  """Configure global clients (sync and async) once per process.
117
119
 
@@ -73,17 +73,17 @@ __all__ = [
73
73
  "reset_client",
74
74
  # Module-level functions
75
75
  "load_tasks",
76
- "list_envs",
76
+ "list_envs",
77
77
  "list_regions",
78
78
  "environment",
79
79
  "make",
80
80
  "make_for_task",
81
81
  "instances",
82
- "instance",
82
+ "instance",
83
83
  "delete",
84
84
  "load_tasks_from_file",
85
85
  "load_task_array_from_string",
86
- "load_task_from_string",
86
+ "load_task_from_string",
87
87
  "load_task_from_json",
88
88
  "export_tasks",
89
89
  "import_tasks",
@@ -108,9 +108,13 @@ async def environment(env_key: str) -> Environment:
108
108
  return await _async_global_client.get_client().environment(env_key)
109
109
 
110
110
 
111
- async def make(env_key: str, region: Optional[str] = None, env_variables: Optional[Dict[str, Any]] = None) -> AsyncEnv:
111
+ async def make(
112
+ env_key: str,
113
+ region: Optional[str] = None,
114
+ env_variables: Optional[Dict[str, Any]] = None,
115
+ ) -> AsyncEnv:
112
116
  """Create a new environment instance.
113
-
117
+
114
118
  Example:
115
119
  env = await fleet.make("fira")
116
120
  env_with_vars = await fleet.make("fira", env_variables={"LOGGED_IN_NAME": "Alice"})
@@ -123,7 +127,9 @@ async def make_for_task(task: Task) -> AsyncEnv:
123
127
  return await _async_global_client.get_client().make_for_task(task)
124
128
 
125
129
 
126
- async def instances(status: Optional[str] = None, region: Optional[str] = None) -> List[AsyncEnv]:
130
+ async def instances(
131
+ status: Optional[str] = None, region: Optional[str] = None
132
+ ) -> List[AsyncEnv]:
127
133
  """List existing environment instances."""
128
134
  return await _async_global_client.get_client().instances(status, region)
129
135
 
@@ -140,7 +146,7 @@ async def delete(instance_id: str) -> InstanceResponse:
140
146
 
141
147
  async def load_tasks_from_file(filename: str) -> List[Task]:
142
148
  """Load tasks from a JSON file.
143
-
149
+
144
150
  Example:
145
151
  tasks = await fleet.load_tasks_from_file("my_tasks.json")
146
152
  """
@@ -149,16 +155,18 @@ async def load_tasks_from_file(filename: str) -> List[Task]:
149
155
 
150
156
  async def load_task_array_from_string(serialized_tasks: str) -> List[Task]:
151
157
  """Load tasks from a JSON string containing an array of tasks.
152
-
158
+
153
159
  Example:
154
160
  tasks = await fleet.load_task_array_from_string(json_string)
155
161
  """
156
- return await _async_global_client.get_client().load_task_array_from_string(serialized_tasks)
162
+ return await _async_global_client.get_client().load_task_array_from_string(
163
+ serialized_tasks
164
+ )
157
165
 
158
166
 
159
167
  async def load_task_from_string(task_string: str) -> Task:
160
168
  """Load a single task from a JSON string.
161
-
169
+
162
170
  Example:
163
171
  task = await fleet.load_task_from_string(task_json_string)
164
172
  """
@@ -167,16 +175,18 @@ async def load_task_from_string(task_string: str) -> Task:
167
175
 
168
176
  async def load_task_from_json(task_json: dict) -> Task:
169
177
  """Load a single task from a dictionary.
170
-
178
+
171
179
  Example:
172
180
  task = await fleet.load_task_from_json(task_dict)
173
181
  """
174
182
  return await _async_global_client.get_client().load_task_from_json(task_json)
175
183
 
176
184
 
177
- async def export_tasks(env_key: Optional[str] = None, filename: Optional[str] = None) -> Optional[str]:
185
+ async def export_tasks(
186
+ env_key: Optional[str] = None, filename: Optional[str] = None
187
+ ) -> Optional[str]:
178
188
  """Export tasks to a JSON file.
179
-
189
+
180
190
  Example:
181
191
  await fleet.export_tasks("fira", "fira_tasks.json")
182
192
  """
@@ -185,7 +195,7 @@ async def export_tasks(env_key: Optional[str] = None, filename: Optional[str] =
185
195
 
186
196
  async def import_tasks(filename: str):
187
197
  """Import tasks from a JSON file.
188
-
198
+
189
199
  Example:
190
200
  await fleet.import_tasks("tasks.json")
191
201
  """
@@ -200,8 +210,8 @@ async def account() -> AccountResponse:
200
210
  def configure(
201
211
  api_key: Optional[str] = None,
202
212
  base_url: Optional[str] = None,
203
- max_retries: int | None = None,
204
- timeout: float | None = None,
213
+ max_retries: Optional[int] = None,
214
+ timeout: Optional[float] = None,
205
215
  ):
206
216
  """Configure global async client once per process.
207
217
 
@@ -213,9 +223,11 @@ def configure(
213
223
  """
214
224
  if max_retries is None:
215
225
  from ..config import DEFAULT_MAX_RETRIES as _MR
226
+
216
227
  max_retries = _MR
217
228
  if timeout is None:
218
229
  from ..config import DEFAULT_TIMEOUT as _TO
230
+
219
231
  timeout = _TO
220
232
  _async_global_client.configure(
221
233
  api_key=api_key, base_url=base_url, max_retries=max_retries, timeout=timeout
@@ -130,7 +130,7 @@ class AsyncEnv(EnvironmentBase):
130
130
  return await self.instance.verify(validator)
131
131
 
132
132
  async def verify_raw(
133
- self, function_code: str, function_name: str | None = None
133
+ self, function_code: str, function_name: Optional[str] = None
134
134
  ) -> ExecuteFunctionResponse:
135
135
  return await self.instance.verify_raw(function_code, function_name)
136
136
 
@@ -304,12 +304,19 @@ class AsyncFleet:
304
304
 
305
305
  return await self.load_task_array_from_string(tasks_data)
306
306
 
307
- async def load_task_array_from_string(
308
- self, serialized_tasks: List[Dict]
309
- ) -> List[Task]:
307
+ async def load_task_array_from_string(self, serialized_tasks: str) -> List[Task]:
310
308
  tasks = []
311
309
 
312
- json_tasks = json.loads(serialized_tasks)
310
+ parsed_data = json.loads(serialized_tasks)
311
+ if isinstance(parsed_data, list):
312
+ json_tasks = parsed_data
313
+ elif isinstance(parsed_data, dict) and "tasks" in parsed_data:
314
+ json_tasks = parsed_data["tasks"]
315
+ else:
316
+ raise ValueError(
317
+ "Invalid JSON structure: expected array or object with 'tasks' key"
318
+ )
319
+
313
320
  for json_task in json_tasks:
314
321
  parsed_task = await self.load_task_from_json(json_task)
315
322
  tasks.append(parsed_task)
@@ -321,25 +328,46 @@ class AsyncFleet:
321
328
 
322
329
  async def load_task_from_json(self, task_json: Dict) -> Task:
323
330
  verifier = None
331
+ verifier_code = task_json.get("verifier_func") or task_json.get("verifier_code")
332
+
333
+ # Try to find verifier_id in multiple locations
334
+ verifier_id = task_json.get("verifier_id")
335
+ if (
336
+ not verifier_id
337
+ and "metadata" in task_json
338
+ and isinstance(task_json["metadata"], dict)
339
+ ):
340
+ verifier_metadata = task_json["metadata"].get("verifier", {})
341
+ if isinstance(verifier_metadata, dict):
342
+ verifier_id = verifier_metadata.get("verifier_id")
343
+
344
+ # If no verifier_id found, use the task key/id as fallback
345
+ if not verifier_id:
346
+ verifier_id = task_json.get("key", task_json.get("id"))
347
+
324
348
  try:
325
- if "verifier_id" in task_json and task_json["verifier_id"]:
349
+ if verifier_id and verifier_code:
326
350
  verifier = await self._create_verifier_from_data(
327
- verifier_id=task_json["verifier_id"],
328
- verifier_key=task_json["key"],
329
- verifier_code=task_json["verifier_func"],
351
+ verifier_id=verifier_id,
352
+ verifier_key=task_json.get("key", task_json.get("id")),
353
+ verifier_code=verifier_code,
330
354
  verifier_sha=task_json.get("verifier_sha", ""),
331
355
  )
332
356
  except Exception as e:
333
- logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
357
+ logger.warning(
358
+ f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
359
+ )
334
360
 
335
361
  task = Task(
336
- key=task_json["key"],
362
+ key=task_json.get("key", task_json.get("id")),
337
363
  prompt=task_json["prompt"],
338
- env_id=task_json["env_id"], # Use env_id from the data
339
- created_at=task_json["created_at"],
364
+ env_id=task_json.get(
365
+ "env_id", task_json.get("env_key")
366
+ ), # Use env_id or fallback to env_key
367
+ created_at=task_json.get("created_at"),
340
368
  version=task_json.get("version"),
341
369
  env_variables=task_json.get("env_variables", {}),
342
- verifier_func=task_json.get("verifier_func"), # Set verifier code
370
+ verifier_func=verifier_code, # Set verifier code
343
371
  verifier=verifier, # Use created verifier or None
344
372
  metadata=task_json.get("metadata", {}), # Default empty metadata
345
373
  )
@@ -377,7 +405,7 @@ class AsyncFleet:
377
405
  # Prepare verifier loading coroutines with concurrency limit
378
406
  verifier_coroutines = []
379
407
  task_responses_with_indices = []
380
- semaphore = asyncio.Semaphore(10) # Limit to 10 concurrent operations
408
+ semaphore = asyncio.Semaphore(100) # Limit to 10 concurrent operations
381
409
 
382
410
  for idx, task_response in enumerate(task_list_response.tasks):
383
411
  if task_response.verifier:
@@ -632,7 +660,7 @@ class AsyncFleet:
632
660
  AsyncVerifierFunction created from the verifier code
633
661
  """
634
662
  # Fetch verifier from API
635
- response = await self.client.request("GET", f"/v1/verifier/{verifier_id}")
663
+ response = await self.client.request("GET", f"/v1/verifiers/{verifier_id}")
636
664
  verifier_data = response.json()
637
665
 
638
666
  # Use the common method to create verifier
@@ -108,7 +108,7 @@ class AsyncInstanceClient:
108
108
  return await self.verify_raw(function_code, function_name)
109
109
 
110
110
  async def verify_raw(
111
- self, function_code: str, function_name: str | None = None
111
+ self, function_code: str, function_name: Optional[str] = None
112
112
  ) -> ExecuteFunctionResponse:
113
113
  try:
114
114
  function_code = convert_verifier_string(function_code)
@@ -1,4 +1,4 @@
1
- from typing import Any, List, Optional
1
+ from typing import Any, List, Optional, Dict, Tuple
2
2
  from ...instance.models import Resource as ResourceModel
3
3
  from ...instance.models import DescribeResponse, QueryRequest, QueryResponse
4
4
  from .base import Resource
@@ -25,12 +25,12 @@ from fleet.verifiers.db import (
25
25
  class AsyncDatabaseSnapshot:
26
26
  """Async database snapshot that fetches data through API and stores locally for diffing."""
27
27
 
28
- def __init__(self, resource: "AsyncSQLiteResource", name: str | None = None):
28
+ def __init__(self, resource: "AsyncSQLiteResource", name: Optional[str] = None):
29
29
  self.resource = resource
30
30
  self.name = name or f"snapshot_{datetime.utcnow().isoformat()}"
31
31
  self.created_at = datetime.utcnow()
32
- self._data: dict[str, list[dict[str, Any]]] = {}
33
- self._schemas: dict[str, list[str]] = {}
32
+ self._data: Dict[str, List[Dict[str, Any]]] = {}
33
+ self._schemas: Dict[str, List[str]] = {}
34
34
  self._fetched = False
35
35
 
36
36
  async def _ensure_fetched(self):
@@ -69,7 +69,7 @@ class AsyncDatabaseSnapshot:
69
69
 
70
70
  self._fetched = True
71
71
 
72
- async def tables(self) -> list[str]:
72
+ async def tables(self) -> List[str]:
73
73
  """Get list of all tables in the snapshot."""
74
74
  await self._ensure_fetched()
75
75
  return list(self._data.keys())
@@ -81,7 +81,7 @@ class AsyncDatabaseSnapshot:
81
81
  async def diff(
82
82
  self,
83
83
  other: "AsyncDatabaseSnapshot",
84
- ignore_config: IgnoreConfig | None = None,
84
+ ignore_config: Optional[IgnoreConfig] = None,
85
85
  ) -> "AsyncSnapshotDiff":
86
86
  """Compare this snapshot with another."""
87
87
  await self._ensure_fetched()
@@ -95,13 +95,13 @@ class AsyncSnapshotQueryBuilder:
95
95
  def __init__(self, snapshot: AsyncDatabaseSnapshot, table: str):
96
96
  self._snapshot = snapshot
97
97
  self._table = table
98
- self._select_cols: list[str] = ["*"]
99
- self._conditions: list[tuple[str, str, Any]] = []
100
- self._limit: int | None = None
101
- self._order_by: str | None = None
98
+ self._select_cols: List[str] = ["*"]
99
+ self._conditions: List[Tuple[str, str, Any]] = []
100
+ self._limit: Optional[int] = None
101
+ self._order_by: Optional[str] = None
102
102
  self._order_desc: bool = False
103
103
 
104
- async def _get_data(self) -> list[dict[str, Any]]:
104
+ async def _get_data(self) -> List[Dict[str, Any]]:
105
105
  """Get table data from snapshot."""
106
106
  await self._snapshot._ensure_fetched()
107
107
  return self._snapshot._data.get(self._table, [])
@@ -122,11 +122,11 @@ class AsyncSnapshotQueryBuilder:
122
122
  qb._order_desc = desc
123
123
  return qb
124
124
 
125
- async def first(self) -> dict[str, Any] | None:
125
+ async def first(self) -> Optional[Dict[str, Any]]:
126
126
  rows = await self.all()
127
127
  return rows[0] if rows else None
128
128
 
129
- async def all(self) -> list[dict[str, Any]]:
129
+ async def all(self) -> List[Dict[str, Any]]:
130
130
  data = await self._get_data()
131
131
 
132
132
  # Apply filters
@@ -185,14 +185,14 @@ class AsyncSnapshotDiff:
185
185
  self,
186
186
  before: AsyncDatabaseSnapshot,
187
187
  after: AsyncDatabaseSnapshot,
188
- ignore_config: IgnoreConfig | None = None,
188
+ ignore_config: Optional[IgnoreConfig] = None,
189
189
  ):
190
190
  self.before = before
191
191
  self.after = after
192
192
  self.ignore_config = ignore_config or IgnoreConfig()
193
- self._cached: dict[str, Any] | None = None
193
+ self._cached: Optional[Dict[str, Any]] = None
194
194
 
195
- async def _get_primary_key_columns(self, table: str) -> list[str]:
195
+ async def _get_primary_key_columns(self, table: str) -> List[str]:
196
196
  """Get primary key columns for a table."""
197
197
  # Try to get from schema
198
198
  schema_response = await self.after.resource.query(f"PRAGMA table_info({table})")
@@ -222,7 +222,7 @@ class AsyncSnapshotDiff:
222
222
  return self._cached
223
223
 
224
224
  all_tables = set(await self.before.tables()) | set(await self.after.tables())
225
- diff: dict[str, dict[str, Any]] = {}
225
+ diff: Dict[str, Dict[str, Any]] = {}
226
226
 
227
227
  for tbl in all_tables:
228
228
  if self.ignore_config.should_ignore_table(tbl):
@@ -236,7 +236,7 @@ class AsyncSnapshotDiff:
236
236
  after_data = self.after._data.get(tbl, [])
237
237
 
238
238
  # Create indexes by primary key
239
- def make_key(row: dict, pk_cols: list[str]) -> Any:
239
+ def make_key(row: dict, pk_cols: List[str]) -> Any:
240
240
  if len(pk_cols) == 1:
241
241
  return row.get(pk_cols[0])
242
242
  return tuple(row.get(col) for col in pk_cols)
@@ -304,12 +304,12 @@ class AsyncSnapshotDiff:
304
304
  self._cached = diff
305
305
  return diff
306
306
 
307
- async def expect_only(self, allowed_changes: list[dict[str, Any]]):
307
+ async def expect_only(self, allowed_changes: List[Dict[str, Any]]):
308
308
  """Ensure only specified changes occurred."""
309
309
  diff = await self._collect()
310
310
 
311
311
  def _is_change_allowed(
312
- table: str, row_id: Any, field: str | None, after_value: Any
312
+ table: str, row_id: Any, field: Optional[str], after_value: Any
313
313
  ) -> bool:
314
314
  """Check if a change is in the allowed list using semantic comparison."""
315
315
  for allowed in allowed_changes:
@@ -440,11 +440,11 @@ class AsyncQueryBuilder:
440
440
  def __init__(self, resource: "AsyncSQLiteResource", table: str):
441
441
  self._resource = resource
442
442
  self._table = table
443
- self._select_cols: list[str] = ["*"]
444
- self._conditions: list[tuple[str, str, Any]] = []
445
- self._joins: list[tuple[str, dict[str, str]]] = []
446
- self._limit: int | None = None
447
- self._order_by: str | None = None
443
+ self._select_cols: List[str] = ["*"]
444
+ self._conditions: List[Tuple[str, str, Any]] = []
445
+ self._joins: List[Tuple[str, Dict[str, str]]] = []
446
+ self._limit: Optional[int] = None
447
+ self._order_by: Optional[str] = None
448
448
 
449
449
  # Column projection / limiting / ordering
450
450
  def select(self, *columns: str) -> "AsyncQueryBuilder":
@@ -486,12 +486,12 @@ class AsyncQueryBuilder:
486
486
  def lte(self, column: str, value: Any) -> "AsyncQueryBuilder":
487
487
  return self._add_condition(column, "<=", value)
488
488
 
489
- def in_(self, column: str, values: list[Any]) -> "AsyncQueryBuilder":
489
+ def in_(self, column: str, values: List[Any]) -> "AsyncQueryBuilder":
490
490
  qb = self._clone()
491
491
  qb._conditions.append((column, "IN", tuple(values)))
492
492
  return qb
493
493
 
494
- def not_in(self, column: str, values: list[Any]) -> "AsyncQueryBuilder":
494
+ def not_in(self, column: str, values: List[Any]) -> "AsyncQueryBuilder":
495
495
  qb = self._clone()
496
496
  qb._conditions.append((column, "NOT IN", tuple(values)))
497
497
  return qb
@@ -508,16 +508,16 @@ class AsyncQueryBuilder:
508
508
  return qb
509
509
 
510
510
  # JOIN
511
- def join(self, other_table: str, on: dict[str, str]) -> "AsyncQueryBuilder":
511
+ def join(self, other_table: str, on: Dict[str, str]) -> "AsyncQueryBuilder":
512
512
  qb = self._clone()
513
513
  qb._joins.append((other_table, on))
514
514
  return qb
515
515
 
516
516
  # Compile to SQL
517
- def _compile(self) -> tuple[str, list[Any]]:
517
+ def _compile(self) -> Tuple[str, List[Any]]:
518
518
  cols = ", ".join(self._select_cols)
519
519
  sql = [f"SELECT {cols} FROM {self._table}"]
520
- params: list[Any] = []
520
+ params: List[Any] = []
521
521
 
522
522
  # Joins
523
523
  for tbl, onmap in self._joins:
@@ -558,11 +558,11 @@ class AsyncQueryBuilder:
558
558
  return row_dict.get("__cnt__", 0)
559
559
  return 0
560
560
 
561
- async def first(self) -> dict[str, Any] | None:
561
+ async def first(self) -> Optional[Dict[str, Any]]:
562
562
  rows = await self.limit(1).all()
563
563
  return rows[0] if rows else None
564
564
 
565
- async def all(self) -> list[dict[str, Any]]:
565
+ async def all(self) -> List[Dict[str, Any]]:
566
566
  sql, params = self._compile()
567
567
  response = await self._resource.query(sql, params)
568
568
  if not response.rows:
@@ -674,7 +674,7 @@ class AsyncSQLiteResource(Resource):
674
674
  """Create a query builder for the specified table."""
675
675
  return AsyncQueryBuilder(self, table_name)
676
676
 
677
- async def snapshot(self, name: str | None = None) -> AsyncDatabaseSnapshot:
677
+ async def snapshot(self, name: Optional[str] = None) -> AsyncDatabaseSnapshot:
678
678
  """Create a snapshot of the current database state."""
679
679
  snapshot = AsyncDatabaseSnapshot(self, name)
680
680
  await snapshot._ensure_fetched()
@@ -683,7 +683,7 @@ class AsyncSQLiteResource(Resource):
683
683
  async def diff(
684
684
  self,
685
685
  other: "AsyncSQLiteResource",
686
- ignore_config: IgnoreConfig | None = None,
686
+ ignore_config: Optional[IgnoreConfig] = None,
687
687
  ) -> AsyncSnapshotDiff:
688
688
  """Compare this database with another AsyncSQLiteResource.
689
689
 
@@ -44,7 +44,7 @@ class Task(BaseModel):
44
44
  @property
45
45
  def env_key(self) -> str:
46
46
  """Get the environment key combining env_id and version."""
47
- if self.version and self.version != "None":
47
+ if self.version and self.version != "None" and ":" not in self.env_id:
48
48
  return f"{self.env_id}:{self.version}"
49
49
  return self.env_id
50
50
 
@@ -174,6 +174,18 @@ def verifier_from_string(
174
174
  raise ValueError(f"Failed to create verifier from string: {e}")
175
175
 
176
176
 
177
+ async def load_tasks_from_file(filename: str) -> List[Task]:
178
+ """Load tasks from a JSON file.
179
+
180
+ Example:
181
+ tasks = await fleet.load_tasks_from_file("my_tasks.json")
182
+ """
183
+ from .global_client import get_client
184
+
185
+ client = get_client()
186
+ return await client.load_tasks_from_file(filename)
187
+
188
+
177
189
  async def load_tasks(
178
190
  env_key: Optional[str] = None,
179
191
  keys: Optional[List[str]] = None,
@@ -12,7 +12,7 @@ import uuid
12
12
  import logging
13
13
  import hashlib
14
14
  import asyncio
15
- from typing import Any, Callable, Dict, Optional, List, TypeVar, Set
15
+ from typing import Any, Callable, Dict, Optional, List, TypeVar, Set, Tuple
16
16
 
17
17
  from .bundler import FunctionBundler
18
18
  from ..client import AsyncEnv
@@ -56,7 +56,7 @@ class AsyncVerifierFunction:
56
56
  # Copy function metadata
57
57
  functools.update_wrapper(self, func)
58
58
 
59
- def _get_or_create_bundle(self) -> tuple[bytes, str]:
59
+ def _get_or_create_bundle(self) -> Tuple[bytes, str]:
60
60
  """Get or create bundle data and return (bundle_data, sha)."""
61
61
  if self._bundle_data is None or self._bundle_sha is None:
62
62
  # If we have raw code, create a bundle from it
@@ -98,7 +98,7 @@ class AsyncVerifierFunction:
98
98
 
99
99
  return self._bundle_data, self._bundle_sha
100
100
 
101
- async def _check_bundle_status(self, env: AsyncEnv) -> tuple[str, bool]:
101
+ async def _check_bundle_status(self, env: AsyncEnv) -> Tuple[str, bool]:
102
102
  """Check if bundle needs to be uploaded and return (sha, needs_upload)."""
103
103
  bundle_data, bundle_sha = self._get_or_create_bundle()
104
104
 
@@ -130,7 +130,7 @@ class SyncEnv(EnvironmentBase):
130
130
  return self.instance.verify(validator)
131
131
 
132
132
  def verify_raw(
133
- self, function_code: str, function_name: str | None = None
133
+ self, function_code: str, function_name: Optional[str] = None
134
134
  ) -> ExecuteFunctionResponse:
135
135
  return self.instance.verify_raw(function_code, function_name)
136
136
 
@@ -302,10 +302,19 @@ class Fleet:
302
302
 
303
303
  return self.load_task_array_from_string(tasks_data)
304
304
 
305
- def load_task_array_from_string(self, serialized_tasks: List[Dict]) -> List[Task]:
305
+ def load_task_array_from_string(self, serialized_tasks: str) -> List[Task]:
306
306
  tasks = []
307
307
 
308
- json_tasks = json.loads(serialized_tasks)
308
+ parsed_data = json.loads(serialized_tasks)
309
+ if isinstance(parsed_data, list):
310
+ json_tasks = parsed_data
311
+ elif isinstance(parsed_data, dict) and "tasks" in parsed_data:
312
+ json_tasks = parsed_data["tasks"]
313
+ else:
314
+ raise ValueError(
315
+ "Invalid JSON structure: expected array or object with 'tasks' key"
316
+ )
317
+
309
318
  for json_task in json_tasks:
310
319
  parsed_task = self.load_task_from_json(json_task)
311
320
  tasks.append(parsed_task)
@@ -316,25 +325,47 @@ class Fleet:
316
325
  return self.load_task_from_json(task_json)
317
326
 
318
327
  def load_task_from_json(self, task_json: Dict) -> Task:
328
+ verifier = None
329
+ verifier_code = task_json.get("verifier_func") or task_json.get("verifier_code")
330
+
331
+ # Try to find verifier_id in multiple locations
332
+ verifier_id = task_json.get("verifier_id")
333
+ if (
334
+ not verifier_id
335
+ and "metadata" in task_json
336
+ and isinstance(task_json["metadata"], dict)
337
+ ):
338
+ verifier_metadata = task_json["metadata"].get("verifier", {})
339
+ if isinstance(verifier_metadata, dict):
340
+ verifier_id = verifier_metadata.get("verifier_id")
341
+
342
+ # If no verifier_id found, use the task key/id as fallback
343
+ if not verifier_id:
344
+ verifier_id = task_json.get("key", task_json.get("id"))
345
+
319
346
  try:
320
- if "verifier_id" in task_json and task_json["verifier_id"]:
347
+ if verifier_id and verifier_code:
321
348
  verifier = self._create_verifier_from_data(
322
- verifier_id=task_json["verifier_id"],
323
- verifier_key=task_json["key"],
324
- verifier_code=task_json["verifier_func"],
349
+ verifier_id=verifier_id,
350
+ verifier_key=task_json.get("key", task_json.get("id")),
351
+ verifier_code=verifier_code,
325
352
  verifier_sha=task_json.get("verifier_sha", ""),
326
353
  )
327
354
  except Exception as e:
328
- logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
355
+ logger.warning(
356
+ f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
357
+ )
329
358
 
330
359
  task = Task(
331
- key=task_json["key"],
360
+ key=task_json.get("key", task_json.get("id")),
332
361
  prompt=task_json["prompt"],
333
- env_id=task_json["env_id"], # Use env_id from the data
334
- created_at=task_json["created_at"],
362
+ env_id=task_json.get(
363
+ "env_id", task_json.get("env_key")
364
+ ), # Use env_id or fallback to env_key
365
+ created_at=task_json.get("created_at"),
335
366
  version=task_json.get("version"),
336
367
  env_variables=task_json.get("env_variables", {}),
337
- verifier_func=task_json.get("verifier_func"), # Set verifier code
368
+ verifier_func=verifier_code, # Set verifier code
338
369
  verifier=verifier, # Use created verifier or None
339
370
  metadata=task_json.get("metadata", {}), # Default empty metadata
340
371
  )
@@ -636,7 +667,7 @@ class Fleet:
636
667
  AsyncVerifierFunction created from the verifier code
637
668
  """
638
669
  # Fetch verifier from API
639
- response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
670
+ response = self.client.request("GET", f"/v1/verifiers/{verifier_id}")
640
671
  verifier_data = response.json()
641
672
 
642
673
  # Use the common method to create verifier
@@ -63,9 +63,7 @@ class InstanceClient:
63
63
  def load(self) -> None:
64
64
  self._load_resources()
65
65
 
66
- def reset(
67
- self, reset_request: Optional[ResetRequest] = None
68
- ) -> ResetResponse:
66
+ def reset(self, reset_request: Optional[ResetRequest] = None) -> ResetResponse:
69
67
  response = self.client.request(
70
68
  "POST", "/reset", json=reset_request.model_dump() if reset_request else None
71
69
  )
@@ -108,7 +106,7 @@ class InstanceClient:
108
106
  return self.verify_raw(function_code, function_name)
109
107
 
110
108
  def verify_raw(
111
- self, function_code: str, function_name: str | None = None
109
+ self, function_code: str, function_name: Optional[str] = None
112
110
  ) -> ExecuteFunctionResponse:
113
111
  try:
114
112
  function_code = convert_verifier_string(function_code)