fleet-python 0.2.46__tar.gz → 0.2.48__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.46 → fleet_python-0.2.48}/PKG-INFO +1 -1
  2. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/client.py +3 -0
  3. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/models.py +6 -0
  4. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/tasks.py +70 -7
  5. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/verifiers/verifier.py +78 -66
  6. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/client.py +62 -72
  7. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/instance/client.py +3 -1
  8. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/models.py +7 -3
  9. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/resources/sqlite.py +9 -3
  10. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/tasks.py +81 -31
  11. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/parse.py +3 -3
  12. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/verifier.py +84 -83
  13. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet_python.egg-info/PKG-INFO +1 -1
  14. {fleet_python-0.2.46 → fleet_python-0.2.48}/pyproject.toml +1 -1
  15. {fleet_python-0.2.46 → fleet_python-0.2.48}/LICENSE +0 -0
  16. {fleet_python-0.2.46 → fleet_python-0.2.48}/README.md +0 -0
  17. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/diff_example.py +0 -0
  18. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/dsl_example.py +0 -0
  19. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example.py +0 -0
  20. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/exampleResume.py +0 -0
  21. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_account.py +0 -0
  22. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_action_log.py +0 -0
  23. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_client.py +0 -0
  24. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_mcp_anthropic.py +0 -0
  25. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_mcp_openai.py +0 -0
  26. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_sync.py +0 -0
  27. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_task.py +0 -0
  28. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_tasks.py +0 -0
  29. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/example_verifier.py +0 -0
  30. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/gemini_example.py +0 -0
  31. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/json_tasks_example.py +0 -0
  32. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/nova_act_example.py +0 -0
  33. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/openai_example.py +0 -0
  34. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/openai_simple_example.py +0 -0
  35. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/query_builder_example.py +0 -0
  36. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/quickstart.py +0 -0
  37. {fleet_python-0.2.46 → fleet_python-0.2.48}/examples/test_cdp_logging.py +0 -0
  38. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/__init__.py +0 -0
  39. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/__init__.py +0 -0
  40. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/base.py +0 -0
  41. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/env/__init__.py +0 -0
  42. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/env/client.py +0 -0
  43. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/exceptions.py +0 -0
  44. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/global_client.py +0 -0
  45. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/instance/__init__.py +0 -0
  46. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/instance/base.py +0 -0
  47. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/instance/client.py +0 -0
  48. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/resources/__init__.py +0 -0
  49. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/resources/base.py +0 -0
  50. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/resources/browser.py +0 -0
  51. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/resources/mcp.py +0 -0
  52. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/resources/sqlite.py +0 -0
  53. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/verifiers/__init__.py +0 -0
  54. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/_async/verifiers/bundler.py +0 -0
  55. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/base.py +0 -0
  56. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/config.py +0 -0
  57. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/env/__init__.py +0 -0
  58. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/env/client.py +0 -0
  59. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/exceptions.py +0 -0
  60. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/global_client.py +0 -0
  61. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/instance/__init__.py +0 -0
  62. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/instance/base.py +0 -0
  63. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/instance/models.py +0 -0
  64. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/resources/__init__.py +0 -0
  65. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/resources/base.py +0 -0
  66. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/resources/browser.py +0 -0
  67. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/resources/mcp.py +0 -0
  68. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/types.py +0 -0
  69. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/__init__.py +0 -0
  70. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/bundler.py +0 -0
  71. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/code.py +0 -0
  72. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/db.py +0 -0
  73. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/decorator.py +0 -0
  74. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet/verifiers/sql_differ.py +0 -0
  75. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet_python.egg-info/SOURCES.txt +0 -0
  76. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet_python.egg-info/dependency_links.txt +0 -0
  77. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet_python.egg-info/requires.txt +0 -0
  78. {fleet_python-0.2.46 → fleet_python-0.2.48}/fleet_python.egg-info/top_level.txt +0 -0
  79. {fleet_python-0.2.46 → fleet_python-0.2.48}/scripts/fix_sync_imports.py +0 -0
  80. {fleet_python-0.2.46 → fleet_python-0.2.48}/scripts/unasync.py +0 -0
  81. {fleet_python-0.2.46 → fleet_python-0.2.48}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.46
3
+ Version: 0.2.48
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -379,6 +379,7 @@ class AsyncFleet:
379
379
  keys: Optional[List[str]] = None,
380
380
  version: Optional[str] = None,
381
381
  team_id: Optional[str] = None,
382
+ project_key: Optional[str] = None,
382
383
  ) -> List[Task]:
383
384
  """Load tasks for the authenticated team, with optional filtering.
384
385
 
@@ -398,6 +399,8 @@ class AsyncFleet:
398
399
  params["task_keys"] = keys
399
400
  if team_id is not None:
400
401
  params["team_id"] = team_id
402
+ if project_key is not None:
403
+ params["project_key"] = project_key
401
404
 
402
405
  response = await self.client.request("GET", "/v1/tasks", params=params)
403
406
  task_list_response = TaskListResponse(**response.json())
@@ -273,6 +273,12 @@ class VerifiersExecuteResponse(BaseModel):
273
273
  result: Optional[Any] = Field(
274
274
  None, description="The return value of the function", title="Result"
275
275
  )
276
+ verifier_id: Optional[str] = Field(
277
+ None, description="ID of the verifier", title="Verifier Id"
278
+ )
279
+ execution_id: Optional[str] = Field(
280
+ None, description="ID of the execution record", title="Execution Id"
281
+ )
276
282
  error: Optional[Dict[str, Any]] = Field(
277
283
  None, description="Error details if verification failed", title="Error"
278
284
  )
@@ -3,13 +3,16 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from datetime import datetime
6
- from typing import Any, Dict, Optional, List
6
+ from typing import Any, Dict, Optional, List, TYPE_CHECKING
7
7
 
8
8
  from pydantic import BaseModel, Field, validator
9
9
 
10
10
  # Import the shared VerifierFunction type that works for both async and sync
11
11
  from fleet.types import VerifierFunction
12
12
 
13
+ if TYPE_CHECKING:
14
+ from fleet._async.models import VerifiersExecuteResponse
15
+
13
16
 
14
17
  class Task(BaseModel):
15
18
  """A task model representing a single task in the Fleet system."""
@@ -24,9 +27,9 @@ class Task(BaseModel):
24
27
  version: Optional[str] = Field(None, description="Task version")
25
28
  verifier_func: Optional[str] = Field(None, description="Verifier function code")
26
29
  verifier: Optional[Any] = Field(
27
- None,
30
+ None,
28
31
  description="Verifier function with decorator (async or sync)",
29
- exclude=True # Exclude from JSON serialization
32
+ exclude=True, # Exclude from JSON serialization
30
33
  )
31
34
  verifier_id: Optional[str] = Field(None, description="Verifier identifier")
32
35
  verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
@@ -68,7 +71,7 @@ class Task(BaseModel):
68
71
  # If verifier doesn't exist but verifier_func does, rebuild it
69
72
  if not self.verifier and self.verifier_func:
70
73
  self._rebuild_verifier()
71
-
74
+
72
75
  if self.verifier:
73
76
  import asyncio
74
77
  import inspect
@@ -102,7 +105,7 @@ class Task(BaseModel):
102
105
  # If verifier doesn't exist but verifier_func does, rebuild it
103
106
  if not self.verifier and self.verifier_func:
104
107
  self._rebuild_verifier()
105
-
108
+
106
109
  if self.verifier:
107
110
  result = self.verifier.remote(*args, **kwargs)
108
111
  # If it's a coroutine, await it
@@ -115,6 +118,65 @@ class Task(BaseModel):
115
118
  else:
116
119
  raise ValueError("No verifier function found for this task")
117
120
 
121
+ async def verify_detailed_async(
122
+ self, *args, **kwargs
123
+ ) -> "VerifiersExecuteResponse":
124
+ """Verify the task and return the full execute response model.
125
+
126
+ For async environments, awaits the async verifier.
127
+ Works with both sync and async verifiers in async contexts.
128
+ """
129
+ # If verifier doesn't exist but verifier_func does, rebuild it
130
+ if not self.verifier and self.verifier_func:
131
+ self._rebuild_verifier()
132
+
133
+ if self.verifier:
134
+ result = self.verifier.remote_with_response(*args, **kwargs)
135
+ # If it's a coroutine, await it
136
+ import inspect
137
+
138
+ if inspect.iscoroutine(result):
139
+ return await result
140
+ else:
141
+ return result
142
+ else:
143
+ raise ValueError("No verifier function found for this task")
144
+
145
+ def verify_detailed(self, env, *args, **kwargs) -> "VerifiersExecuteResponse":
146
+ """Verify the task and return the full execute response model (sync version).
147
+
148
+ For sync environments, calls the sync verifier directly.
149
+ For async verifiers, automatically runs them with asyncio.run().
150
+ """
151
+ # If verifier doesn't exist but verifier_func does, rebuild it
152
+ if not self.verifier and self.verifier_func:
153
+ self._rebuild_verifier()
154
+
155
+ if self.verifier:
156
+ import asyncio
157
+ import inspect
158
+
159
+ # Check if verifier has remote_with_response method (for decorated verifiers)
160
+ result = self.verifier.remote_with_response(env, *args, **kwargs)
161
+
162
+ # If the result is a coroutine, we need to run it
163
+ if inspect.iscoroutine(result):
164
+ # Check if we're already in an event loop
165
+ try:
166
+ asyncio.get_running_loop()
167
+ # We're in an async context, can't use asyncio.run()
168
+ raise RuntimeError(
169
+ "Cannot run async verifier in sync mode while event loop is running. "
170
+ "Use await task.verify_detailed_async() instead."
171
+ )
172
+ except RuntimeError:
173
+ # No event loop running, safe to use asyncio.run()
174
+ return asyncio.run(result)
175
+ else:
176
+ return result
177
+ else:
178
+ raise ValueError("No verifier function found for this task")
179
+
118
180
  def _rebuild_verifier(self):
119
181
  """Rebuild the verifier from verifier_func string if it exists."""
120
182
  if self.verifier_func:
@@ -127,7 +189,7 @@ class Task(BaseModel):
127
189
  sha256=self.verifier_sha or "",
128
190
  )
129
191
  self.verifier = verifier
130
-
192
+
131
193
  async def make_env(self, region: Optional[str] = None):
132
194
  """Create an environment instance for this task's environment.
133
195
 
@@ -214,6 +276,7 @@ async def load_tasks(
214
276
  keys: Optional[List[str]] = None,
215
277
  version: Optional[str] = None,
216
278
  team_id: Optional[str] = None,
279
+ project_key: Optional[str] = None,
217
280
  ) -> List[Task]:
218
281
  """Convenience function to load tasks with optional filtering.
219
282
 
@@ -233,7 +296,7 @@ async def load_tasks(
233
296
 
234
297
  client = get_client()
235
298
  return await client.load_tasks(
236
- env_key=env_key, keys=keys, version=version, team_id=team_id
299
+ env_key=env_key, keys=keys, version=version, team_id=team_id, project_key=project_key
237
300
  )
238
301
 
239
302
 
@@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, Optional, List, TypeVar, Tuple
16
16
 
17
17
  from .bundler import FunctionBundler
18
18
  from ..client import AsyncEnv
19
+ from ...models import VerifiersExecuteResponse
19
20
 
20
21
  logger = logging.getLogger(__name__)
21
22
 
@@ -152,72 +153,15 @@ class AsyncVerifierFunction:
152
153
 
153
154
  async def remote(self, env: AsyncEnv, *args, **kwargs) -> float:
154
155
  """Remote execution of the verifier function with SHA-based bundle caching."""
155
- # Async verifiers are now supported by the backend
156
- # if self._is_async:
157
- # raise NotImplementedError(
158
- # f"Async verifier '{self.key}' cannot be executed remotely. "
159
- # "The remote execution environment only supports synchronous functions. "
160
- # "Please provide a synchronous version of your verifier."
161
- # )
156
+ response = await self.remote_with_response(env, *args, **kwargs)
162
157
 
163
- args_array = list(args)
164
- args_array.append({"env": env.instance_id})
165
- args = tuple(args_array)
166
-
167
- try:
168
- # Check if bundle needs to be uploaded
169
- bundle_sha, needs_upload = await self._check_bundle_status(env)
170
-
171
- if needs_upload:
172
- # Need to upload bundle to S3
173
- logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
174
- bundle_data, _ = self._get_or_create_bundle()
175
-
176
- response = await env.execute_verifier_remote(
177
- bundle_data=bundle_data,
178
- bundle_sha=bundle_sha,
179
- key=self.key,
180
- function_name=self.func.__name__,
181
- args=args,
182
- args_array=args_array,
183
- kwargs=kwargs,
184
- needs_upload=True,
185
- )
186
-
187
- logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
188
-
189
- else:
190
- # Bundle already available - execute without upload
191
- logger.info(
192
- f"Executing cached bundle {bundle_sha[:8]}... for {self.key}"
193
- )
194
- bundle_data, _ = self._get_or_create_bundle()
195
-
196
- response = await env.execute_verifier_remote(
197
- bundle_data=bundle_data or b"", # Empty if using server-side bundle
198
- bundle_sha=bundle_sha,
199
- key=self.key,
200
- function_name=self.func.__name__,
201
- args=args,
202
- args_array=args_array,
203
- kwargs=kwargs,
204
- needs_upload=False, # Don't upload, just execute
205
- )
206
-
207
- # Handle response
208
- if response.stdout:
209
- print(response.stdout)
210
- if response.success:
211
- return self._process_result(response.result)
212
- else:
213
- self._raise_remote_error(response.error)
214
-
215
- except Exception as e:
216
- logger.error(f"Remote execution failed for {self.key}: {e}")
217
- # If it's an HTTP error, try to get more details
218
- if hasattr(e, "response") and hasattr(e.response, "text"):
219
- logger.error(f"Server response: {e.response.text}")
220
- raise
158
+ # Handle response
159
+ if response.stdout:
160
+ print(response.stdout)
161
+ if response.success:
162
+ return self._process_result(response.result)
163
+ else:
164
+ self._raise_remote_error(response.error)
221
165
 
222
166
  def _process_result(self, result: Any) -> float:
223
167
  """Process remote execution result, handling different return types."""
@@ -257,7 +201,7 @@ Remote traceback:
257
201
  try:
258
202
  exception_class = getattr(__builtins__, error_type, RuntimeError)
259
203
  raise exception_class(full_message)
260
- except:
204
+ except Exception:
261
205
  raise RuntimeError(full_message)
262
206
 
263
207
  def _get_env_id(self, env: AsyncEnv) -> str:
@@ -280,6 +224,74 @@ Remote traceback:
280
224
  or "not found" in error_msg
281
225
  )
282
226
 
227
+ async def remote_with_response(
228
+ self, env: "AsyncEnv", *args, **kwargs
229
+ ) -> "VerifiersExecuteResponse":
230
+ """Remote execution of the verifier function that returns the full response model."""
231
+ args_array = list(args)
232
+ args_array.append({"env": env.instance_id})
233
+ args = tuple(args_array)
234
+
235
+ try:
236
+ # Check if bundle needs to be uploaded
237
+ bundle_sha, needs_upload = await self._check_bundle_status(env)
238
+
239
+ if needs_upload:
240
+ # Need to upload bundle to S3
241
+ logger.info(f"Uploading bundle {bundle_sha[:8]}... for {self.key}")
242
+ bundle_data, _ = self._get_or_create_bundle()
243
+
244
+ response = await env.execute_verifier_remote(
245
+ bundle_data=bundle_data,
246
+ bundle_sha=bundle_sha,
247
+ key=self.key,
248
+ function_name=self.func.__name__,
249
+ args=args,
250
+ args_array=args_array,
251
+ kwargs=kwargs,
252
+ needs_upload=True,
253
+ )
254
+
255
+ logger.debug(f"Bundle {bundle_sha[:8]}... uploaded successfully")
256
+
257
+ else:
258
+ # Bundle already available - execute without upload
259
+ logger.info(f"Bundle {bundle_sha[:8]}... already cached for {self.key}")
260
+ response = await env.execute_verifier_remote(
261
+ bundle_data=b"", # Empty bundle since it's cached
262
+ bundle_sha=bundle_sha,
263
+ key=self.key,
264
+ function_name=self.func.__name__,
265
+ args=args,
266
+ args_array=args_array,
267
+ kwargs=kwargs,
268
+ needs_upload=False,
269
+ )
270
+
271
+ return response
272
+
273
+ except Exception as e:
274
+ # Check if error indicates bundle not found and retry with upload
275
+ if self._is_bundle_not_found_error(e) and not needs_upload:
276
+ logger.info(
277
+ f"Bundle {bundle_sha[:8]}... not found on server, uploading..."
278
+ )
279
+ bundle_data, _ = self._get_or_create_bundle()
280
+ response = await env.execute_verifier_remote(
281
+ bundle_data=bundle_data,
282
+ bundle_sha=bundle_sha,
283
+ key=self.key,
284
+ function_name=self.func.__name__,
285
+ args=args,
286
+ args_array=args_array,
287
+ kwargs=kwargs,
288
+ needs_upload=True,
289
+ )
290
+ return response
291
+ else:
292
+ logger.error(f"Error in remote execution of {self.key}: {e}")
293
+ raise
294
+
283
295
 
284
296
  def verifier(
285
297
  key: Optional[str] = None,
@@ -16,7 +16,6 @@
16
16
 
17
17
  import base64
18
18
  import cloudpickle
19
- import concurrent.futures
20
19
  import httpx
21
20
  import json
22
21
  import logging
@@ -291,7 +290,9 @@ class Fleet:
291
290
  def execute_verifier_remote(
292
291
  self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
293
292
  ) -> VerifiersExecuteResponse:
294
- return _execute_verifier_remote(self.client, bundle_data, args, kwargs, timeout)
293
+ return _execute_verifier_remote(
294
+ self.client, bundle_data, args, kwargs, timeout
295
+ )
295
296
 
296
297
  def delete(self, instance_id: str) -> InstanceResponse:
297
298
  return _delete_instance(self.client, instance_id)
@@ -377,6 +378,7 @@ class Fleet:
377
378
  keys: Optional[List[str]] = None,
378
379
  version: Optional[str] = None,
379
380
  team_id: Optional[str] = None,
381
+ project_key: Optional[str] = None,
380
382
  ) -> List[Task]:
381
383
  """Load tasks for the authenticated team, with optional filtering.
382
384
 
@@ -396,13 +398,16 @@ class Fleet:
396
398
  params["task_keys"] = keys
397
399
  if team_id is not None:
398
400
  params["team_id"] = team_id
401
+ if project_key is not None:
402
+ params["project_key"] = project_key
399
403
 
400
404
  response = self.client.request("GET", "/v1/tasks", params=params)
401
405
  task_list_response = TaskListResponse(**response.json())
402
406
 
403
- # Prepare verifier loading tasks
404
- verifier_tasks = []
407
+ # Prepare verifier loading coroutines with concurrency limit
408
+ verifier_coroutines = []
405
409
  task_responses_with_indices = []
410
+ semaphore = asyncio.Semaphore(100) # Limit to 10 concurrent operations
406
411
 
407
412
  for idx, task_response in enumerate(task_list_response.tasks):
408
413
  if task_response.verifier:
@@ -413,74 +418,61 @@ class Fleet:
413
418
 
414
419
  def create_verifier_with_fallback(tr, emb_code, is_error):
415
420
  """Create verifier with fallback logic."""
416
- if not is_error:
417
- # Try to create from embedded data
418
- try:
419
- return self._create_verifier_from_data(
420
- verifier_id=tr.verifier.verifier_id,
421
- verifier_key=tr.verifier.key,
422
- verifier_code=emb_code,
423
- verifier_sha=tr.verifier.sha256,
424
- )
425
- except Exception as e:
426
- logger.warning(
427
- f"Failed to create verifier {tr.verifier.key}: {e}"
428
- )
429
- return None
430
- else:
431
- # Fallback: try fetching by ID
432
- try:
433
- logger.warning(
434
- f"Embedded verifier code missing for {tr.verifier.key} (NoSuchKey). "
435
- f"Attempting to refetch by id {tr.verifier.verifier_id}"
436
- )
437
- return self._load_verifier(tr.verifier.verifier_id)
438
- except Exception as e:
439
- logger.warning(
440
- f"Refetch by verifier id failed for {tr.verifier.key}: {e}. "
441
- "Leaving verifier unset."
442
- )
443
- return None
444
-
445
- # Add the task for parallel execution
446
- verifier_tasks.append(
447
- (
448
- create_verifier_with_fallback,
449
- task_response,
450
- embedded_code,
451
- is_embedded_error,
421
+ with semaphore: # Acquire semaphore before operation
422
+ if not is_error:
423
+ # Try to create from embedded data
424
+ try:
425
+ return self._create_verifier_from_data(
426
+ verifier_id=tr.verifier.verifier_id,
427
+ verifier_key=tr.verifier.key,
428
+ verifier_code=emb_code,
429
+ verifier_sha=tr.verifier.sha256,
430
+ )
431
+ except Exception as e:
432
+ logger.warning(
433
+ f"Failed to create verifier {tr.verifier.key}: {e}"
434
+ )
435
+ return None
436
+ else:
437
+ # Fallback: try fetching by ID
438
+ try:
439
+ logger.warning(
440
+ f"Embedded verifier code missing for {tr.verifier.key} (NoSuchKey). "
441
+ f"Attempting to refetch by id {tr.verifier.verifier_id}"
442
+ )
443
+ return self._load_verifier(
444
+ tr.verifier.verifier_id
445
+ )
446
+ except Exception as e:
447
+ logger.warning(
448
+ f"Refetch by verifier id failed for {tr.verifier.key}: {e}. "
449
+ "Leaving verifier unset."
450
+ )
451
+ return None
452
+
453
+ # Add the coroutine for parallel execution
454
+ verifier_coroutines.append(
455
+ create_verifier_with_fallback(
456
+ task_response, embedded_code, is_embedded_error
452
457
  )
453
458
  )
454
459
  task_responses_with_indices.append((idx, task_response))
455
460
  else:
456
461
  # No verifier needed
457
- verifier_tasks.append(None)
462
+ verifier_coroutines.append(None)
458
463
  task_responses_with_indices.append((idx, task_response))
459
464
 
460
- # Execute all verifier loading in parallel using ThreadPoolExecutor
461
- verifier_results = []
462
- if verifier_tasks:
463
- with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
464
- futures = []
465
- for task in verifier_tasks:
466
- if task is not None:
467
- func, tr, emb_code, is_error = task
468
- future = executor.submit(func, tr, emb_code, is_error)
469
- futures.append(future)
470
- else:
471
- futures.append(None)
472
-
473
- # Collect results
474
- for future in futures:
475
- if future is None:
476
- verifier_results.append(None)
477
- else:
478
- try:
479
- result = future.result()
480
- verifier_results.append(result)
481
- except Exception as e:
482
- logger.warning(f"Verifier loading failed: {e}")
483
- verifier_results.append(None)
465
+ # Execute all verifier loading in parallel
466
+ if verifier_coroutines:
467
+ verifier_results = asyncio.gather(
468
+ *[
469
+ coro if coro is not None else time.sleep(0)
470
+ for coro in verifier_coroutines
471
+ ],
472
+ return_exceptions=True,
473
+ )
474
+ else:
475
+ verifier_results = []
484
476
 
485
477
  # Build tasks with results
486
478
  tasks = []
@@ -493,7 +485,11 @@ class Fleet:
493
485
 
494
486
  if task_response.verifier:
495
487
  # Process verifier result
496
- if verifier_result is not None:
488
+ if isinstance(verifier_result, Exception):
489
+ logger.warning(
490
+ f"Verifier loading failed for {task_response.key}: {verifier_result}"
491
+ )
492
+ elif verifier_result is not None:
497
493
  verifier = verifier_result
498
494
  embedded_code = task_response.verifier.code or ""
499
495
  is_embedded_error = embedded_code.strip().startswith(
@@ -579,8 +575,6 @@ class Fleet:
579
575
  task = Task(**task_data)
580
576
  tasks.append(task)
581
577
 
582
- responses = []
583
-
584
578
  for task in tasks:
585
579
  payload = TaskRequest(
586
580
  key=task.key,
@@ -594,13 +588,10 @@ class Fleet:
594
588
  response = self.client.request(
595
589
  "POST", "/v1/tasks", json=payload.model_dump()
596
590
  )
597
- responses.append(response)
598
591
  except Exception as e:
599
592
  logger.error(f"Failed to import task {task.key}: {e}")
600
593
  continue
601
594
 
602
- return responses
603
-
604
595
  def account(self) -> AccountResponse:
605
596
  """Get account information including instance limits and usage.
606
597
 
@@ -647,7 +638,6 @@ class Fleet:
647
638
  AsyncVerifierFunction created from the verifier code
648
639
  """
649
640
  from .tasks import verifier_from_string
650
- from .verifiers import SyncVerifierFunction
651
641
 
652
642
  # Use verifier_from_string to create the verifier
653
643
  verifier_func = verifier_from_string(
@@ -63,7 +63,9 @@ class InstanceClient:
63
63
  def load(self) -> None:
64
64
  self._load_resources()
65
65
 
66
- def reset(self, reset_request: Optional[ResetRequest] = None) -> ResetResponse:
66
+ def reset(
67
+ self, reset_request: Optional[ResetRequest] = None
68
+ ) -> ResetResponse:
67
69
  response = self.client.request(
68
70
  "POST", "/reset", json=reset_request.model_dump() if reset_request else None
69
71
  )
@@ -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="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")
@@ -275,6 +273,12 @@ class VerifiersExecuteResponse(BaseModel):
275
273
  result: Optional[Any] = Field(
276
274
  None, description="The return value of the function", title="Result"
277
275
  )
276
+ verifier_id: Optional[str] = Field(
277
+ None, description="ID of the verifier", title="Verifier Id"
278
+ )
279
+ execution_id: Optional[str] = Field(
280
+ None, description="ID of the execution record", title="Execution Id"
281
+ )
278
282
  error: Optional[Dict[str, Any]] = Field(
279
283
  None, description="Error details if verification failed", title="Error"
280
284
  )
@@ -651,7 +651,9 @@ class SQLiteResource(Resource):
651
651
  )
652
652
  return DescribeResponse(**response.json())
653
653
 
654
- def query(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
654
+ def query(
655
+ self, query: str, args: Optional[List[Any]] = None
656
+ ) -> QueryResponse:
655
657
  return self._query(query, args, read_only=True)
656
658
 
657
659
  def exec(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
@@ -693,8 +695,12 @@ class SQLiteResource(Resource):
693
695
  AsyncSnapshotDiff: Object containing the differences between the two databases
694
696
  """
695
697
  # Create snapshots of both databases
696
- before_snapshot = self.snapshot(name=f"before_{datetime.utcnow().isoformat()}")
697
- after_snapshot = other.snapshot(name=f"after_{datetime.utcnow().isoformat()}")
698
+ before_snapshot = self.snapshot(
699
+ name=f"before_{datetime.utcnow().isoformat()}"
700
+ )
701
+ after_snapshot = other.snapshot(
702
+ name=f"after_{datetime.utcnow().isoformat()}"
703
+ )
698
704
 
699
705
  # Return the diff between the snapshots
700
706
  return before_snapshot.diff(after_snapshot, ignore_config)