fleet-python 0.2.32__py3-none-any.whl → 0.2.34__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,191 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interactive Browser Snapshot/Resume Example
4
+
5
+ This script:
6
+ 1. Creates an environment and browser
7
+ 2. Lets you interact with it manually
8
+ 3. Takes a snapshot when you type 'close'
9
+ 4. Resumes from the snapshot with a new environment
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ from datetime import datetime
16
+
17
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
18
+
19
+ from fleet import Fleet
20
+
21
+ def main():
22
+ # Initialize Fleet client
23
+ api_key = os.getenv("FLEET_API_KEY")
24
+ if not api_key:
25
+ print("Error: FLEET_API_KEY environment variable not set")
26
+ sys.exit(1)
27
+
28
+ fleet = Fleet(api_key=api_key)
29
+
30
+ # Initialize environment variables
31
+ env = None
32
+ new_env = None
33
+
34
+ try:
35
+ # Step 1: Create environment and browser
36
+ print("🚀 Creating new environment...")
37
+ env_key = "fira:v1.3.2"
38
+
39
+ env = fleet.make(env_key)
40
+ print(f"✅ Environment created: {env.instance_id}")
41
+ print(f"📝 Session ID: {env.session_id}")
42
+
43
+ # Start browser
44
+ print("\n🌐 Starting browser...")
45
+ browser = env.browser()
46
+ browser.start(width=1920, height=1080)
47
+
48
+ # Get browser URLs with session ID for automatic logging
49
+ cdp_page_url = browser.cdp_page_url()
50
+ cdp_browser_url = browser.cdp_browser_url()
51
+ devtools_url = browser.devtools_url()
52
+
53
+ print(f"✅ Browser started!")
54
+ print(f"🔗 CDP Page URL: {cdp_page_url}")
55
+ print(f"🔗 CDP Browser URL: {cdp_browser_url}")
56
+ print(f"🔗 DevTools URL: {devtools_url}")
57
+
58
+ # Show connection info for debugging
59
+ conn_info = browser.get_connection_info()
60
+ print(f"📊 Logging enabled: {conn_info['logging_enabled']}")
61
+ if conn_info['logging_enabled']:
62
+ print(f"📝 All CDP actions will be automatically logged to session: {env.session_id}")
63
+
64
+ # Step 2: Wait for user interaction
65
+ print("\n" + "="*60)
66
+ print("🎮 INTERACTIVE MODE")
67
+ print("="*60)
68
+ print("You can now interact with the browser manually.")
69
+ print("Open the DevTools URL in Chrome to see and control the browser.")
70
+ print("\nType 'close' and press Enter when you're done to save a snapshot.")
71
+ print("="*60 + "\n")
72
+
73
+ while True:
74
+ user_input = input(">>> ").strip().lower()
75
+
76
+ if user_input == "close":
77
+ break
78
+ elif user_input == "status":
79
+ print(f"Environment: {env.instance_id}")
80
+ print(f"Session: {env.session_id}")
81
+ else:
82
+ print("Type 'close' to save snapshot and exit, or 'status' to see current info")
83
+
84
+ # Step 3: Take snapshot
85
+ print("\n📸 Taking snapshot...")
86
+ snapshot = env.get_snapshot(browser)
87
+
88
+ # Save snapshot to file
89
+ snapshot_file = f"snapshot_{env.session_id}_{int(datetime.now().timestamp())}.json"
90
+ with open(snapshot_file, "w") as f:
91
+ json.dump(snapshot.model_dump(), f, indent=2)
92
+
93
+ print(f"✅ Snapshot saved to: {snapshot_file}")
94
+ print(f" - Tool logs: {len(snapshot.tool_logs)} entries")
95
+ print(f" - Action logs: {len(snapshot.action_logs)} entries")
96
+ print(f" - Page URL: {snapshot.page_url}")
97
+ print(f" - Viewport: {snapshot.viewport_size}")
98
+
99
+ # Close original environment
100
+ print("\n🏁 Closing original environment...")
101
+ env.close()
102
+ env = None # Mark as closed
103
+
104
+ # Step 4: Resume from snapshot
105
+ print("\n" + "="*60)
106
+ print("🔄 RESUMING FROM SNAPSHOT")
107
+ print("="*60)
108
+
109
+ input("\nPress Enter to resume from snapshot...")
110
+
111
+ print("\n🚀 Creating new environment from snapshot...")
112
+ new_env, validation = fleet.resume(
113
+ snapshot,
114
+ validate=True,
115
+ playback_speed=2.0 # Replay at 2x speed
116
+ )
117
+
118
+ print(f"✅ Environment resumed: {new_env.instance_id}")
119
+ print(f"📝 New session ID: {new_env.session_id}")
120
+
121
+ # Show validation results
122
+ print(f"\n📊 Validation Results:")
123
+ print(f" - Success: {validation.success}")
124
+ print(f" - Page match: {validation.page_match}")
125
+ print(f" - Action log match: {validation.action_log_match}")
126
+
127
+ if validation.discrepancies:
128
+ print(f" - Discrepancies ({len(validation.discrepancies)}):")
129
+ for disc in validation.discrepancies[:5]: # Show first 5
130
+ print(f" • {disc}")
131
+ if len(validation.discrepancies) > 5:
132
+ print(f" • ... and {len(validation.discrepancies) - 5} more")
133
+
134
+ # Get new browser reference
135
+ new_browser = new_env.browser()
136
+ print(f"\n🌐 New browser ready!")
137
+ print(f"🔗 CDP Page URL: {new_browser.cdp_page_url()}")
138
+ print(f"🔗 DevTools URL: {new_browser.devtools_url()}")
139
+ print(f"📝 Resume session logging to: {new_env.session_id}")
140
+
141
+ # Step 5: Keep new environment open for inspection
142
+ print("\n" + "="*60)
143
+ print("🎮 RESUMED ENVIRONMENT READY")
144
+ print("="*60)
145
+ print("The resumed environment is now ready. You can inspect it to verify")
146
+ print("it matches your original state.")
147
+ print("\nType 'done' when finished to close the resumed environment.")
148
+ print("="*60 + "\n")
149
+
150
+ while True:
151
+ user_input = input(">>> ").strip().lower()
152
+
153
+ if user_input == "done":
154
+ break
155
+ elif user_input == "status":
156
+ print(f"Environment: {new_env.instance_id}")
157
+ print(f"Session: {new_env.session_id}")
158
+ else:
159
+ print("Type 'done' to close the environment, or 'status' to see current info")
160
+
161
+ # Clean up
162
+ print("\n🏁 Closing resumed environment...")
163
+ new_env.close()
164
+ new_env = None # Mark as closed
165
+ print("✅ All done!")
166
+
167
+ except KeyboardInterrupt:
168
+ print("\n\n⚠️ Interrupted by user")
169
+ except Exception as e:
170
+ print(f"\n❌ Error: {e}")
171
+ finally:
172
+ # Always clean up environments
173
+ if env is not None:
174
+ try:
175
+ print("\n🧹 Cleaning up original environment...")
176
+ env.close()
177
+ print("✅ Original environment closed")
178
+ except Exception as e:
179
+ print(f"⚠️ Failed to close original environment: {e}")
180
+
181
+ if new_env is not None:
182
+ try:
183
+ print("\n🧹 Cleaning up resumed environment...")
184
+ new_env.close()
185
+ print("✅ Resumed environment closed")
186
+ except Exception as e:
187
+ print(f"⚠️ Failed to close resumed environment: {e}")
188
+
189
+
190
+ if __name__ == "__main__":
191
+ main()
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test CDP Logging Example
4
+
5
+ This script demonstrates the new automatic CDP logging functionality.
6
+ All CDP commands and events are now logged automatically at the proxy level.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ from datetime import datetime
12
+
13
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
14
+
15
+ from fleet import Fleet
16
+
17
+ def main():
18
+ # Initialize Fleet client
19
+ api_key = os.getenv("FLEET_API_KEY")
20
+ if not api_key:
21
+ print("Error: FLEET_API_KEY environment variable not set")
22
+ sys.exit(1)
23
+
24
+ fleet = Fleet(api_key=api_key)
25
+ env = None
26
+
27
+ try:
28
+ # Create environment
29
+ print("🚀 Creating new environment...")
30
+ env_key = "fira:v1.3.2" # Replace with your actual environment key
31
+
32
+ env = fleet.make(env_key)
33
+ print(f"✅ Environment created: {env.instance_id}")
34
+ print(f"📝 Session ID: {env.session_id}")
35
+
36
+ # Start browser
37
+ print("\n🌐 Starting browser with automatic CDP logging...")
38
+ browser = env.browser()
39
+ browser.start(width=1920, height=1080)
40
+
41
+ # Show connection information
42
+ conn_info = browser.get_connection_info()
43
+ print("\n📊 Connection Information:")
44
+ print(f" Session ID: {conn_info['session_id']}")
45
+ print(f" CDP Page URL: {conn_info['cdp_page_url']}")
46
+ print(f" CDP Browser URL: {conn_info['cdp_browser_url']}")
47
+ print(f" Logging Enabled: {conn_info['logging_enabled']}")
48
+
49
+ if conn_info['logging_enabled']:
50
+ print(f"\n✅ All CDP actions will be automatically logged!")
51
+ print(f" - Connect any CDP client to: {conn_info['cdp_page_url']}")
52
+ print(f" - All commands and events will be logged to session: {env.session_id}")
53
+ print(f" - Use DevTools at: {conn_info['devtools_url']}")
54
+ else:
55
+ print(f"\n⚠️ Logging not enabled - no session ID available")
56
+
57
+ print("\n" + "="*60)
58
+ print("🎯 LOGGING TEST COMPLETE")
59
+ print("="*60)
60
+ print("The browser is ready with automatic CDP logging enabled.")
61
+ print("Connect any CDP client or Playwright to the URLs above.")
62
+ print("All actions will be automatically captured in the tool_log table.")
63
+ print("\nPress Enter to close the environment...")
64
+ print("="*60 + "\n")
65
+
66
+ input()
67
+
68
+ except Exception as e:
69
+ print(f"\n❌ Error: {e}")
70
+ finally:
71
+ if env is not None:
72
+ try:
73
+ print("\n🧹 Cleaning up environment...")
74
+ env.close()
75
+ print("✅ Environment closed")
76
+ except Exception as e:
77
+ print(f"⚠️ Failed to close environment: {e}")
78
+
79
+ if __name__ == "__main__":
80
+ main()
fleet/client.py CHANGED
@@ -50,7 +50,7 @@ from .instance.client import ValidatorType
50
50
  from .resources.base import Resource
51
51
  from .resources.sqlite import SQLiteResource
52
52
  from .resources.browser import BrowserResource
53
- from .resources.mcp import MCPResource
53
+ from ..resources.mcp import MCPResource
54
54
 
55
55
  logger = logging.getLogger(__name__)
56
56
 
@@ -254,7 +254,9 @@ class Fleet:
254
254
  def execute_verifier_remote(
255
255
  self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
256
256
  ) -> VerifiersExecuteResponse:
257
- return _execute_verifier_remote(self.client, bundle_data, args, kwargs, timeout)
257
+ return _execute_verifier_remote(
258
+ self.client, bundle_data, args, kwargs, timeout
259
+ )
258
260
 
259
261
  def delete(self, instance_id: str) -> InstanceResponse:
260
262
  return _delete_instance(self.client, instance_id)
@@ -265,7 +267,9 @@ class Fleet:
265
267
 
266
268
  return self.load_task_array_from_string(tasks_data)
267
269
 
268
- def load_task_array_from_string(self, serialized_tasks: List[Dict]) -> List[Task]:
270
+ def load_task_array_from_string(
271
+ self, serialized_tasks: List[Dict]
272
+ ) -> List[Task]:
269
273
  tasks = []
270
274
 
271
275
  json_tasks = json.loads(serialized_tasks)
@@ -303,11 +307,20 @@ class Fleet:
303
307
  )
304
308
  return task
305
309
 
306
- def load_tasks(self, env_key: Optional[str] = None) -> List[Task]:
307
- """Load tasks for the authenticated team, optionally filtered by environment.
310
+ def load_tasks(
311
+ self,
312
+ env_key: Optional[str] = None,
313
+ keys: Optional[List[str]] = None,
314
+ version: Optional[str] = None,
315
+ team_id: Optional[str] = None
316
+ ) -> List[Task]:
317
+ """Load tasks for the authenticated team, with optional filtering.
308
318
 
309
319
  Args:
310
320
  env_key: Optional environment key to filter tasks by
321
+ keys: Optional list of task keys to filter by
322
+ version: Optional version to filter tasks by (client-side filter)
323
+ team_id: Optional team_id to filter by (admin only)
311
324
 
312
325
  Returns:
313
326
  List[Task] containing Task objects
@@ -315,6 +328,10 @@ class Fleet:
315
328
  params = {}
316
329
  if env_key is not None:
317
330
  params["env_key"] = env_key
331
+ if keys is not None:
332
+ params["task_keys"] = keys
333
+ if team_id is not None:
334
+ params["team_id"] = team_id
318
335
 
319
336
  response = self.client.request("GET", "/v1/tasks", params=params)
320
337
  task_list_response = TaskListResponse(**response.json())
@@ -375,6 +392,10 @@ class Fleet:
375
392
  )
376
393
  tasks.append(task)
377
394
 
395
+ # Apply client-side filtering for version if specified
396
+ if version is not None:
397
+ tasks = [task for task in tasks if task.version == version]
398
+
378
399
  return tasks
379
400
 
380
401
  def export_tasks(
@@ -464,7 +485,7 @@ class Fleet:
464
485
  def _create_verifier_from_data(
465
486
  self, verifier_id: str, verifier_key: str, verifier_code: str, verifier_sha: str
466
487
  ) -> "SyncVerifierFunction":
467
- """Create a SyncVerifierFunction from verifier data.
488
+ """Create an AsyncVerifierFunction from verifier data.
468
489
 
469
490
  Args:
470
491
  verifier_id: The verifier ID
@@ -473,10 +494,16 @@ class Fleet:
473
494
  verifier_sha: The verifier SHA256
474
495
 
475
496
  Returns:
476
- SyncVerifierFunction created from the verifier code
497
+ AsyncVerifierFunction created from the verifier code
477
498
  """
478
- from .tasks import verifier_from_string
499
+ from ..tasks import verifier_from_string
479
500
  from .verifiers.verifier import SyncVerifierFunction
501
+
502
+ # Convert async verifier code to sync
503
+ if 'async def' in verifier_code:
504
+ verifier_code = verifier_code.replace('async def', 'def')
505
+ if 'await ' in verifier_code:
506
+ verifier_code = verifier_code.replace('await ', '')
480
507
 
481
508
  # Use verifier_from_string to create the verifier
482
509
  verifier_func = verifier_from_string(
@@ -486,10 +513,10 @@ class Fleet:
486
513
  sha256=verifier_sha,
487
514
  )
488
515
 
489
- # Ensure we return a SyncVerifierFunction
516
+ # Ensure we return an AsyncVerifierFunction
490
517
  if not isinstance(verifier_func, SyncVerifierFunction):
491
518
  raise TypeError(
492
- f"Expected SyncVerifierFunction but got {type(verifier_func).__name__}"
519
+ f"Expected AsyncVerifierFunction but got {type(verifier_func).__name__}"
493
520
  )
494
521
 
495
522
  # Store the original verifier code for reference
fleet/env/client.py CHANGED
@@ -7,7 +7,7 @@ def make(env_key: str, region: Optional[str] = None) -> SyncEnv:
7
7
  return Fleet().make(env_key, region=region)
8
8
 
9
9
 
10
- def make_for_task(task: Task) -> SyncEnv:
10
+ def make_for_task_async(task: Task) -> SyncEnv:
11
11
  return Fleet().make_for_task(task)
12
12
 
13
13
 
fleet/global_client.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from typing import Optional
4
4
 
5
5
  from .client import Fleet
6
- from .config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
6
+ from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
7
7
 
8
8
 
9
9
  _default_client: Optional[Fleet] = None
fleet/instance/client.py CHANGED
@@ -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
  )
fleet/resources/mcp.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from typing import Dict
2
2
 
3
3
 
4
- class MCPResource:
4
+ class SyncMCPResource:
5
5
  def __init__(self, url: str, env_key: str):
6
6
  self.url = url
7
7
  self._env_key = env_key
@@ -22,33 +22,34 @@ class MCPResource:
22
22
  }
23
23
 
24
24
  def list_tools(self):
25
- import requests
25
+ import aiohttp
26
26
 
27
27
  """
28
- Make a request to list available tools from the MCP endpoint.
29
-
28
+ Make an async request to list available tools from the MCP endpoint.
29
+
30
30
  Returns:
31
31
  List of available tools with name, description, and input_schema
32
32
  """
33
- payload = {"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2}
34
-
35
- response = requests.post(self.url, json=payload)
36
- data = response.json()
37
-
38
- # Extract tools from the response
39
- if "result" in data and "tools" in data["result"]:
40
- tools = data["result"]["tools"]
41
-
42
- available_tools = [
43
- {
44
- "name": tool.get("name"),
45
- "description": tool.get("description"),
46
- "input_schema": tool.get("inputSchema"),
47
- }
48
- for tool in tools
49
- ]
50
-
51
- return available_tools
52
- else:
53
- # Handle error or empty response
54
- return []
33
+ with aiohttp.ClientSession() as session:
34
+ payload = {"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2}
35
+
36
+ with session.post(self.url, json=payload) as response:
37
+ data = response.json()
38
+
39
+ # Extract tools from the response
40
+ if "result" in data and "tools" in data["result"]:
41
+ tools = data["result"]["tools"]
42
+
43
+ available_tools = [
44
+ {
45
+ "name": tool.get("name"),
46
+ "description": tool.get("description"),
47
+ "input_schema": tool.get("inputSchema"),
48
+ }
49
+ for tool in tools
50
+ ]
51
+
52
+ return available_tools
53
+ else:
54
+ # Handle error or empty response
55
+ return []
fleet/resources/sqlite.py CHANGED
@@ -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)
fleet/tasks.py CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import asyncio
6
5
  import re
7
6
  from datetime import datetime
8
7
  from typing import Any, Dict, Optional, List
8
+ from uuid import UUID
9
9
 
10
10
  from pydantic import BaseModel, Field, validator
11
11
 
@@ -13,133 +13,6 @@ from pydantic import BaseModel, Field, validator
13
13
  from fleet.types import VerifierFunction
14
14
 
15
15
 
16
- def verifier_from_string(
17
- verifier_func: str,
18
- verifier_id: Optional[str] = None,
19
- verifier_key: Optional[str] = None,
20
- sha256: Optional[str] = None,
21
- ) -> VerifierFunction:
22
- """Create a verifier function from a string of Python code.
23
-
24
- This function creates either a SyncVerifierFunction or AsyncVerifierFunction
25
- based on whether the code contains async function definitions.
26
-
27
- Args:
28
- verifier_func: String containing the verifier function code
29
- verifier_id: Optional verifier ID to use
30
- verifier_key: Optional verifier key to use (defaults to function name)
31
- sha256: Optional SHA256 hash of existing server-side bundle
32
-
33
- Returns:
34
- VerifierFunction: Either SyncVerifierFunction or AsyncVerifierFunction
35
-
36
- Raises:
37
- ValueError: If function name cannot be extracted from the code
38
- """
39
- from fleet.verifiers.parse import extract_function_name, convert_new_to_old_verifier
40
- from fleet.verifiers.verifier import SyncVerifierFunction
41
- from fleet._async.verifiers.verifier import AsyncVerifierFunction
42
- from fleet.verifiers.db import IgnoreConfig, DatabaseSnapshot
43
-
44
- # Determine if this is an async verifier
45
- is_async = "async def" in verifier_func
46
-
47
- # Store original code for later
48
- original_code = verifier_func
49
-
50
- # Check if this is a new format verifier (has before/after parameters)
51
- if (
52
- "before: DatabaseSnapshot" in verifier_func
53
- and "after: DatabaseSnapshot" in verifier_func
54
- ):
55
- # Convert new format to old format
56
- verifier_func = convert_new_to_old_verifier(verifier_func)
57
- # Update function name since wrapper adds _wrapper suffix
58
- original_name = extract_function_name(verifier_func.split("\n")[0])
59
- if original_name and original_name.endswith("_wrapper"):
60
- function_name = original_name
61
- else:
62
- function_name = extract_function_name(verifier_func)
63
- else:
64
- # Extract function name from code
65
- function_name = extract_function_name(verifier_func)
66
-
67
- if not function_name:
68
- raise ValueError("Could not extract function name from verifier code")
69
-
70
- # Create a namespace for the function
71
- namespace = {
72
- "__builtins__": __builtins__,
73
- "Environment": object, # Placeholder, will be provided at runtime
74
- "IgnoreConfig": IgnoreConfig,
75
- "DatabaseSnapshot": DatabaseSnapshot,
76
- "TASK_FAILED_SCORE": 0,
77
- "TASK_SUCCESSFUL_SCORE": 1,
78
- }
79
-
80
- # Execute the code to create the function
81
- exec(verifier_func, namespace)
82
-
83
- # Get the function from the namespace
84
- if function_name not in namespace:
85
- raise ValueError(f"Function {function_name} not found after execution")
86
-
87
- func = namespace[function_name]
88
-
89
- # Use provided key or default to function name
90
- key = verifier_key or function_name
91
-
92
- # Create appropriate verifier function
93
- if is_async:
94
- # Create AsyncVerifierFunction
95
- return AsyncVerifierFunction(
96
- func=func,
97
- key=key,
98
- verifier_id=verifier_id,
99
- sha256=sha256,
100
- raw_code=original_code,
101
- extra_requirements=None,
102
- )
103
- else:
104
- # For sync verifiers, we need to handle the case where the original was async
105
- # but got converted during unasync processing
106
- if "async def" in original_code and "await " in original_code:
107
- # Convert async code to sync for SyncVerifierFunction
108
- sync_code = original_code.replace("async def", "def")
109
- sync_code = sync_code.replace("await ", "")
110
-
111
- # Re-execute with sync code
112
- namespace = {
113
- "__builtins__": __builtins__,
114
- "Environment": object,
115
- "IgnoreConfig": IgnoreConfig,
116
- "DatabaseSnapshot": DatabaseSnapshot,
117
- "TASK_FAILED_SCORE": 0,
118
- "TASK_SUCCESSFUL_SCORE": 1,
119
- }
120
- exec(sync_code, namespace)
121
- func = namespace[function_name]
122
-
123
- return SyncVerifierFunction(
124
- func=func,
125
- key=key,
126
- verifier_id=verifier_id,
127
- sha256=sha256,
128
- raw_code=sync_code,
129
- extra_requirements=None,
130
- )
131
- else:
132
- # Already sync code
133
- return SyncVerifierFunction(
134
- func=func,
135
- key=key,
136
- verifier_id=verifier_id,
137
- sha256=sha256,
138
- raw_code=original_code,
139
- extra_requirements=None,
140
- )
141
-
142
-
143
16
  class Task(BaseModel):
144
17
  """A task model representing a single task in the Fleet system."""
145
18
 
@@ -206,7 +79,7 @@ class Task(BaseModel):
206
79
  if inspect.iscoroutine(result):
207
80
  # Check if we're already in an event loop
208
81
  try:
209
- _ = asyncio.get_running_loop()
82
+ loop = asyncio.get_running_loop()
210
83
  # We're in an async context, can't use asyncio.run()
211
84
  raise RuntimeError(
212
85
  "Cannot run async verifier in sync mode while event loop is running. "
@@ -251,13 +124,32 @@ class Task(BaseModel):
251
124
  return Fleet().make(env_key=self.env_key, region=region)
252
125
 
253
126
 
254
- def load_tasks(env_key: Optional[str] = None) -> List[Task]:
255
- """Convenience function to load tasks without initializing a client.
127
+ def load_tasks(
128
+ env_key: Optional[str] = None,
129
+ keys: Optional[List[str]] = None,
130
+ version: Optional[str] = None,
131
+ team_id: Optional[str] = None
132
+ ) -> List[Task]:
133
+ """Convenience function to load tasks with optional filtering.
256
134
 
257
- Creates an `AsyncFleet` client under the hood and returns the tasks list.
135
+ Args:
136
+ env_key: Optional environment key to filter tasks by
137
+ keys: Optional list of task keys to filter by
138
+ version: Optional version to filter tasks by
139
+ team_id: Optional team_id to filter by (admin only)
140
+
141
+ Examples:
142
+ tasks = await fleet.load_tasks(env_key="fira")
143
+ tasks = await fleet.load_tasks(keys=["task1", "task2"])
144
+ tasks = await fleet.load_tasks(env_key="fira", version="v1.0")
258
145
  """
259
146
  # Use the global client by default so users can pre-configure it once
260
147
  from .global_client import get_client
261
148
 
262
149
  client = get_client()
263
- return client.load_tasks(env_key=env_key)
150
+ return client.load_tasks(
151
+ env_key=env_key,
152
+ keys=keys,
153
+ version=version,
154
+ team_id=team_id
155
+ )
@@ -162,6 +162,13 @@ class SyncVerifierFunction:
162
162
 
163
163
  def remote(self, env: SyncEnv, *args, **kwargs) -> float:
164
164
  """Remote execution of the verifier function with SHA-based bundle caching."""
165
+ # Async verifiers are now supported by the backend
166
+ # if self._is_async:
167
+ # raise NotImplementedError(
168
+ # f"Async verifier '{self.key}' cannot be executed remotely. "
169
+ # "The remote execution environment only supports synchronous functions. "
170
+ # "Please provide a synchronous version of your verifier."
171
+ # )
165
172
 
166
173
  args_array = list(args)
167
174
  args_array.append({"env": env.instance_id})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -1,6 +1,7 @@
1
1
  examples/diff_example.py,sha256=iLlpBW_NBjzXBqlvYwjx74uxYZkMGJfea6s3tJhvuNY,5684
2
2
  examples/dsl_example.py,sha256=yFLgM-Was4-w575xJgPk9DIBmXa34hLJsIB4XwTADOE,7252
3
3
  examples/example.py,sha256=yn9mqS2yJ6896s25btnJx9-_SLLbyS-Fu-SIcas6jok,1081
4
+ examples/exampleResume.py,sha256=hzdL9QfYtwlje5geWS2cgWgjcnLX4UtXSAd-92F66Lw,7044
4
5
  examples/example_account.py,sha256=t5_Tnr7DcLYfNpEAbuBySQIqsqiQQGySuiItIghCjAM,225
5
6
  examples/example_action_log.py,sha256=pwvLro_Fkrw4DII002bHGuWfoZ6QRvUMDD9BnMqJgLQ,622
6
7
  examples/example_client.py,sha256=M9Mfi1FcD2LtSDVk89R_-tgG98swvDYy4qx2zVayJ-0,1025
@@ -17,14 +18,15 @@ examples/openai_example.py,sha256=dEWERrTEP5xBiGkLkQjBQGd2NqoxX6gcW6XteBPsWFQ,82
17
18
  examples/openai_simple_example.py,sha256=HmiufucrAZne7tHq9uoEsDWlEhjNC265bQAyIGBRU2o,1745
18
19
  examples/query_builder_example.py,sha256=-cOMfWGNifYfYEt_Ds73XpwATZvFDL6F4KTkVxdMjzg,3951
19
20
  examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
21
+ examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
20
22
  fleet/__init__.py,sha256=LYJ8zS6Ldo5wLpRqsFMoiikkyosTmm7sRTUY4SnJAgE,3880
21
23
  fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
22
- fleet/client.py,sha256=WC_fYzf28PXHWAqtp4aE97hbmAeNxzzu1TEbzpgbvMk,21366
24
+ fleet/client.py,sha256=JG9soTIkGK_uRezHU79PFn7PBXeN1iNpun1D4JDFTyE,22310
23
25
  fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
24
26
  fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
25
- fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
27
+ fleet/global_client.py,sha256=oSWhV1cggVKQ0ec0YDOGu6Zr0Tgdcx3oKMM6s2Y9fQw,1073
26
28
  fleet/models.py,sha256=9tDjgcgKPMnf-R_MDh-Ocp_UMbyJ8tJyjb15XqU0N94,12454
27
- fleet/tasks.py,sha256=rNUh-80xq_-Am3WJKNWEtQczBnxgvpAUR4zRd22mJ-4,9771
29
+ fleet/tasks.py,sha256=1AlNhe0WRggj3pIwTeaxMhpmMqI0MGBVjEmODVu37YI,5648
28
30
  fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
29
31
  fleet/_async/__init__.py,sha256=1X00u-0Zu4S-e8MGE9nJWbZEt3H-DHZA-gfwN-JuP2U,7415
30
32
  fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
@@ -47,16 +49,16 @@ fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMT
47
49
  fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
48
50
  fleet/_async/verifiers/verifier.py,sha256=lwVIV5ZpWJhM87tXShtjwN5KP7n5XDcPq0XX7AjV6_E,14343
49
51
  fleet/env/__init__.py,sha256=6-zgP705M61tCquiDKw29dBZGq1US9mtsB1gQngJ4FQ,664
50
- fleet/env/client.py,sha256=WcYc4_k7iUP-CSzhurHqcQXjl43O0C5pliVlk019n_M,799
52
+ fleet/env/client.py,sha256=I4pjnXGzifZSr7iZFdn0cTX6nLuGYo-sCt-qk4ez29Y,805
51
53
  fleet/instance/__init__.py,sha256=CyWUkbGAK-DBPw4DC4AnCW-MqqheGhZMA5QSRVu-ws4,479
52
54
  fleet/instance/base.py,sha256=OYqzBwZFfTX9wlBGSG5gljqj98NbiJeKIfFJ3uj5I4s,1587
53
- fleet/instance/client.py,sha256=6Jc3OMmm7mPit7SWRCf8ZTfqyFgOOMJIawsqRnOY_Nw,5894
55
+ fleet/instance/client.py,sha256=O6B0A2Z0b5SxOLs4TipZ9Ol8yG-b-LG15vVOKMmd6BQ,5908
54
56
  fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
55
57
  fleet/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
58
  fleet/resources/base.py,sha256=AXZzT0_yWHkT497q3yekfr0xsD4cPGMCC6y7C43TIkk,663
57
59
  fleet/resources/browser.py,sha256=hRNM0YMsVQUAraZGNi_B-KXxLpuddy4ntoEDFSw7czU,1295
58
- fleet/resources/mcp.py,sha256=ptWC4sOhk5Slwr7p3USgGX0nM0K98726BxP8BhtSLaU,1486
59
- fleet/resources/sqlite.py,sha256=dof2oQr7-GxscpyIZXlKJvp59AYoiF9_Ghdq5EzdVdY,25705
60
+ fleet/resources/mcp.py,sha256=c6O4vVJnXANuHMGMe4IPxgp4zBEbFaGm6_d9e6j8Myc,1695
61
+ fleet/resources/sqlite.py,sha256=rwpYmfyw8CjlA-HEsD4fCqYS8wFMxaTiX_tO3gJxDjg,25763
60
62
  fleet/verifiers/__init__.py,sha256=IlImvlfgMRQ2V6f4_n7skXYBV8ZoUUjnWaFKnYQQsU8,459
61
63
  fleet/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
62
64
  fleet/verifiers/code.py,sha256=A1i_UabZspbyj1awzKVQ_HRxgMO3fU7NbkxYyTrp7So,48
@@ -64,11 +66,11 @@ fleet/verifiers/db.py,sha256=tssmvJjDHuBIy8qlL_P5-UdmEFUw2DZcqLsWZ8ot3Xw,27766
64
66
  fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,3260
65
67
  fleet/verifiers/parse.py,sha256=0bAbj9VvT__yU4ZVREUK-Tn9dukh9LCpmfVsgj1DfP4,8508
66
68
  fleet/verifiers/sql_differ.py,sha256=dmiGCFXVMEMbAX519OjhVqgA8ZvhnvdmC1BVpL7QCF0,6490
67
- fleet/verifiers/verifier.py,sha256=jbWRqBUX0Cu6NPzU_2yCXAR_Avq1rJdcBIxJPH1Inhs,13896
68
- fleet_python-0.2.32.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
+ fleet/verifiers/verifier.py,sha256=53oBWAf0yy3bZmZx9eH9AWIf65H7OP2UUm0YwWCL6Mc,14286
70
+ fleet_python-0.2.34.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
71
  scripts/fix_sync_imports.py,sha256=0XKTkAV7WdMxRfk8-x4Ts1LjSbUpyI0tPL0DcTQ_38w,7308
70
72
  scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
71
- fleet_python-0.2.32.dist-info/METADATA,sha256=hw7hTd_JKk8DxieuOXfjpLIUb2-R1_65AMMz0Sa1T4c,3354
72
- fleet_python-0.2.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- fleet_python-0.2.32.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
74
- fleet_python-0.2.32.dist-info/RECORD,,
73
+ fleet_python-0.2.34.dist-info/METADATA,sha256=WmolSCZR49qXHttFTyPhRnEJxj6po_YbDeUhOnk4t9M,3354
74
+ fleet_python-0.2.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ fleet_python-0.2.34.dist-info/top_level.txt,sha256=_3DSmTohvSDf3AIP_BYfGzhwO1ECFwuzg83X-wHCx3Y,23
76
+ fleet_python-0.2.34.dist-info/RECORD,,