fleet-python 0.2.12__py3-none-any.whl → 0.2.15__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.
- examples/diff_example.py +161 -0
- examples/dsl_example.py +50 -1
- examples/example.py +1 -1
- examples/example_action_log.py +28 -0
- examples/example_mcp_anthropic.py +77 -0
- examples/example_mcp_openai.py +27 -0
- examples/example_sync.py +1 -1
- examples/example_task.py +199 -0
- examples/example_verifier.py +71 -0
- examples/query_builder_example.py +117 -0
- fleet/__init__.py +51 -40
- fleet/_async/base.py +15 -2
- fleet/_async/client.py +141 -23
- fleet/_async/env/client.py +5 -5
- fleet/_async/instance/__init__.py +2 -3
- fleet/_async/instance/base.py +5 -2
- fleet/_async/instance/client.py +5 -4
- fleet/_async/playwright.py +2 -2
- fleet/_async/resources/base.py +1 -1
- fleet/_async/resources/browser.py +1 -1
- fleet/_async/resources/sqlite.py +656 -2
- fleet/_async/tasks.py +44 -0
- fleet/_async/verifiers/__init__.py +17 -0
- fleet/_async/verifiers/bundler.py +699 -0
- fleet/_async/verifiers/verifier.py +301 -0
- fleet/base.py +14 -1
- fleet/client.py +650 -17
- fleet/config.py +2 -1
- fleet/instance/__init__.py +1 -2
- fleet/instance/base.py +5 -2
- fleet/instance/client.py +16 -6
- fleet/models.py +171 -4
- fleet/resources/browser.py +7 -8
- fleet/resources/mcp.py +60 -0
- fleet/resources/sqlite.py +654 -0
- fleet/tasks.py +44 -0
- fleet/types.py +18 -0
- fleet/verifiers/__init__.py +11 -5
- fleet/verifiers/bundler.py +699 -0
- fleet/verifiers/decorator.py +103 -0
- fleet/verifiers/verifier.py +301 -0
- {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/METADATA +3 -42
- fleet_python-0.2.15.dist-info/RECORD +69 -0
- scripts/fix_sync_imports.py +30 -12
- fleet/_async/config.py +0 -8
- fleet/_async/instance/models.py +0 -141
- fleet/_async/models.py +0 -109
- fleet_python-0.2.12.dist-info/RECORD +0 -55
- {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import fleet as flt
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
load_dotenv()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def main():
|
|
9
|
+
# Create a new instance
|
|
10
|
+
print("Creating new Hubspot instance...")
|
|
11
|
+
env = await flt.env.make_async("hubspot:v1.2.7")
|
|
12
|
+
print(f"New Instance: {env.instance_id}")
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
# Reset the instance
|
|
16
|
+
response = await env.reset(seed=42)
|
|
17
|
+
print(f"Reset response: {response}")
|
|
18
|
+
|
|
19
|
+
# Get the database resource
|
|
20
|
+
db = env.db()
|
|
21
|
+
|
|
22
|
+
# Example 1: Query with the builder pattern
|
|
23
|
+
print("\n=== Query Builder Examples ===")
|
|
24
|
+
|
|
25
|
+
# Find all entries of type 'deal'
|
|
26
|
+
print("\n1. Finding all deals:")
|
|
27
|
+
deals = await db.table("entries").eq("type", "deal").all()
|
|
28
|
+
print(f"Found {len(deals)} deals")
|
|
29
|
+
|
|
30
|
+
# Count entries
|
|
31
|
+
print("\n2. Counting entries:")
|
|
32
|
+
entry_count = await db.table("entries").count()
|
|
33
|
+
print(f"Total entries: {entry_count}")
|
|
34
|
+
|
|
35
|
+
# Find a specific entry
|
|
36
|
+
print("\n3. Finding specific entry:")
|
|
37
|
+
entry = await db.table("entries").eq("id", 1).first()
|
|
38
|
+
if entry:
|
|
39
|
+
print(f"Found entry: {entry['name']} (type: {entry['type']})")
|
|
40
|
+
|
|
41
|
+
# Complex query with multiple conditions
|
|
42
|
+
print("\n4. Complex query with conditions:")
|
|
43
|
+
recent_deals = await (
|
|
44
|
+
db.table("entries")
|
|
45
|
+
.eq("type", "deal")
|
|
46
|
+
.not_null("name")
|
|
47
|
+
.select("id", "name", "type", "createdDate")
|
|
48
|
+
.sort("createdDate", desc=True)
|
|
49
|
+
.limit(5)
|
|
50
|
+
.all()
|
|
51
|
+
)
|
|
52
|
+
print(f"Recent deals: {len(recent_deals)}")
|
|
53
|
+
for deal in recent_deals:
|
|
54
|
+
print(f" - {deal['name']} (id: {deal['id']})")
|
|
55
|
+
|
|
56
|
+
# Using assertions
|
|
57
|
+
print("\n5. Using assertions:")
|
|
58
|
+
try:
|
|
59
|
+
# This should succeed if there are entries
|
|
60
|
+
await db.table("entries").assert_exists()
|
|
61
|
+
print("✓ Entries table has records")
|
|
62
|
+
except AssertionError as e:
|
|
63
|
+
print(f"✗ Assertion failed: {e}")
|
|
64
|
+
|
|
65
|
+
# Check for non-existent record
|
|
66
|
+
try:
|
|
67
|
+
await db.table("entries").eq("id", 999999).assert_none()
|
|
68
|
+
print("✓ No entry with id 999999")
|
|
69
|
+
except AssertionError as e:
|
|
70
|
+
print(f"✗ Assertion failed: {e}")
|
|
71
|
+
|
|
72
|
+
# Insert a new entry and verify with query builder
|
|
73
|
+
print("\n6. Insert and verify with query builder:")
|
|
74
|
+
insert_query = """
|
|
75
|
+
INSERT INTO entries (id, name, type, owner_id, createdDate, lastModifiedDate, createdAt, updatedAt, properties)
|
|
76
|
+
VALUES (
|
|
77
|
+
99999,
|
|
78
|
+
'Test Deal via Query Builder',
|
|
79
|
+
'deal',
|
|
80
|
+
1,
|
|
81
|
+
datetime('now'),
|
|
82
|
+
datetime('now'),
|
|
83
|
+
datetime('now'),
|
|
84
|
+
datetime('now'),
|
|
85
|
+
'{}'
|
|
86
|
+
)
|
|
87
|
+
"""
|
|
88
|
+
await db.exec(insert_query)
|
|
89
|
+
|
|
90
|
+
# Verify insertion with query builder
|
|
91
|
+
new_deal = await db.table("entries").eq("id", 99999).first()
|
|
92
|
+
if new_deal:
|
|
93
|
+
print(f"✓ Successfully inserted: {new_deal['name']}")
|
|
94
|
+
|
|
95
|
+
# Assert specific field value
|
|
96
|
+
await db.table("entries").eq("id", 99999).assert_eq("name", "Test Deal via Query Builder")
|
|
97
|
+
print("✓ Name assertion passed")
|
|
98
|
+
|
|
99
|
+
# Using IN clause
|
|
100
|
+
print("\n7. Using IN clause:")
|
|
101
|
+
specific_entries = await db.table("entries").in_("id", [1, 2, 3]).all()
|
|
102
|
+
print(f"Found {len(specific_entries)} entries with ids in [1, 2, 3]")
|
|
103
|
+
|
|
104
|
+
# Pattern matching with LIKE
|
|
105
|
+
print("\n8. Pattern matching:")
|
|
106
|
+
test_entries = await db.table("entries").ilike("name", "%test%").all()
|
|
107
|
+
print(f"Found {len(test_entries)} entries with 'test' in name")
|
|
108
|
+
|
|
109
|
+
finally:
|
|
110
|
+
# Delete the instance
|
|
111
|
+
print("\n\nDeleting instance...")
|
|
112
|
+
await env.close()
|
|
113
|
+
print("Instance deleted.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
asyncio.run(main())
|
fleet/__init__.py
CHANGED
|
@@ -23,60 +23,71 @@ from .exceptions import (
|
|
|
23
23
|
FleetConfigurationError,
|
|
24
24
|
)
|
|
25
25
|
from .client import Fleet, Environment
|
|
26
|
-
from ._async.client import AsyncFleet,
|
|
27
|
-
from .models import
|
|
28
|
-
from .instance import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ResetResponse,
|
|
32
|
-
CDPDescribeResponse,
|
|
33
|
-
ChromeStartRequest,
|
|
34
|
-
ChromeStartResponse,
|
|
35
|
-
ChromeStatusResponse,
|
|
36
|
-
)
|
|
37
|
-
from ._async.instance import AsyncInstanceClient
|
|
26
|
+
from ._async.client import AsyncFleet, AsyncEnv
|
|
27
|
+
from .models import InstanceRecord
|
|
28
|
+
from .instance.models import Resource, ResetResponse
|
|
29
|
+
|
|
30
|
+
# Import sync verifiers with explicit naming
|
|
38
31
|
from .verifiers import (
|
|
32
|
+
verifier as verifier_sync,
|
|
33
|
+
SyncVerifierFunction,
|
|
39
34
|
DatabaseSnapshot,
|
|
40
35
|
IgnoreConfig,
|
|
41
36
|
SnapshotDiff,
|
|
42
37
|
TASK_SUCCESSFUL_SCORE,
|
|
43
38
|
)
|
|
39
|
+
|
|
40
|
+
# Import Playwright wrapper
|
|
41
|
+
from .playwright import FleetPlaywrightWrapper
|
|
42
|
+
|
|
43
|
+
# Import async verifiers (default verifier is async for modern usage)
|
|
44
|
+
from ._async.verifiers import (
|
|
45
|
+
verifier,
|
|
46
|
+
AsyncVerifierFunction,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Import async tasks (default tasks are async for modern usage)
|
|
50
|
+
from ._async.tasks import Task
|
|
51
|
+
|
|
52
|
+
# Import shared types
|
|
53
|
+
from .types import VerifierFunction
|
|
54
|
+
|
|
55
|
+
# Create a module-level env attribute for convenient access
|
|
44
56
|
from . import env
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
from .playwright import FleetPlaywrightWrapper
|
|
49
|
-
_PLAYWRIGHT_AVAILABLE = True
|
|
50
|
-
except ImportError:
|
|
51
|
-
FleetPlaywrightWrapper = None
|
|
52
|
-
_PLAYWRIGHT_AVAILABLE = False
|
|
58
|
+
__version__ = "0.1.0"
|
|
53
59
|
|
|
54
|
-
__version__ = "0.1.1"
|
|
55
60
|
__all__ = [
|
|
56
|
-
|
|
57
|
-
"FleetError",
|
|
58
|
-
"FleetAPIError",
|
|
59
|
-
"FleetTimeoutError",
|
|
60
|
-
"FleetConfigurationError",
|
|
61
|
+
# Core classes
|
|
61
62
|
"Fleet",
|
|
62
63
|
"Environment",
|
|
63
64
|
"AsyncFleet",
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"ResetRequest",
|
|
65
|
+
"AsyncEnv",
|
|
66
|
+
# Models
|
|
67
|
+
"InstanceRecord",
|
|
68
|
+
"Resource",
|
|
69
69
|
"ResetResponse",
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
70
|
+
# Task models
|
|
71
|
+
"Task",
|
|
72
|
+
"VerifierFunction",
|
|
73
|
+
# Exceptions
|
|
74
|
+
"FleetError",
|
|
75
|
+
"FleetAPIError",
|
|
76
|
+
"FleetTimeoutError",
|
|
77
|
+
"FleetConfigurationError",
|
|
78
|
+
# Playwright wrapper
|
|
79
|
+
"FleetPlaywrightWrapper",
|
|
80
|
+
# Verifiers (async is default)
|
|
81
|
+
"verifier",
|
|
82
|
+
"verifier_sync",
|
|
83
|
+
"AsyncVerifierFunction",
|
|
84
|
+
"SyncVerifierFunction",
|
|
74
85
|
"DatabaseSnapshot",
|
|
75
|
-
"IgnoreConfig",
|
|
86
|
+
"IgnoreConfig",
|
|
76
87
|
"SnapshotDiff",
|
|
77
88
|
"TASK_SUCCESSFUL_SCORE",
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
# Environment module
|
|
90
|
+
"env",
|
|
91
|
+
# Version
|
|
92
|
+
"__version__",
|
|
93
|
+
]
|
fleet/_async/base.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import httpx
|
|
2
2
|
from typing import Dict, Any, Optional
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
|
|
5
|
-
from
|
|
6
|
+
from ..models import InstanceResponse
|
|
7
|
+
from ..config import GLOBAL_BASE_URL
|
|
6
8
|
from .exceptions import (
|
|
7
9
|
FleetAPIError,
|
|
8
10
|
FleetAuthenticationError,
|
|
@@ -18,6 +20,8 @@ from .exceptions import (
|
|
|
18
20
|
FleetPermissionError,
|
|
19
21
|
)
|
|
20
22
|
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
class EnvironmentBase(InstanceResponse):
|
|
23
27
|
@property
|
|
@@ -31,7 +35,7 @@ class BaseWrapper:
|
|
|
31
35
|
raise ValueError("api_key is required")
|
|
32
36
|
self.api_key = api_key
|
|
33
37
|
if base_url is None:
|
|
34
|
-
base_url =
|
|
38
|
+
base_url = GLOBAL_BASE_URL
|
|
35
39
|
self.base_url = base_url
|
|
36
40
|
|
|
37
41
|
def get_headers(self) -> Dict[str, str]:
|
|
@@ -40,6 +44,10 @@ class BaseWrapper:
|
|
|
40
44
|
"X-Fleet-SDK-Version": "1.0.0",
|
|
41
45
|
}
|
|
42
46
|
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
47
|
+
# Debug log
|
|
48
|
+
import logging
|
|
49
|
+
logger = logging.getLogger(__name__)
|
|
50
|
+
logger.debug(f"Headers being sent: {headers}")
|
|
43
51
|
return headers
|
|
44
52
|
|
|
45
53
|
|
|
@@ -81,6 +89,11 @@ class AsyncWrapper(BaseWrapper):
|
|
|
81
89
|
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
82
90
|
"""Handle HTTP error responses and convert to appropriate Fleet exceptions."""
|
|
83
91
|
status_code = response.status_code
|
|
92
|
+
|
|
93
|
+
# Debug log 500 errors
|
|
94
|
+
if status_code == 500:
|
|
95
|
+
logger.error(f"Got 500 error from {response.url}")
|
|
96
|
+
logger.error(f"Response text: {response.text}")
|
|
84
97
|
|
|
85
98
|
# Try to parse error response as JSON
|
|
86
99
|
try:
|
fleet/_async/client.py
CHANGED
|
@@ -14,23 +14,32 @@
|
|
|
14
14
|
|
|
15
15
|
"""Fleet API Client for making HTTP requests to Fleet services."""
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import base64
|
|
18
|
+
import cloudpickle
|
|
18
19
|
import httpx
|
|
19
20
|
import logging
|
|
20
|
-
|
|
21
|
+
import os
|
|
22
|
+
from typing import List, Optional
|
|
21
23
|
|
|
22
24
|
from .base import EnvironmentBase, AsyncWrapper
|
|
23
|
-
from
|
|
25
|
+
from ..models import (
|
|
26
|
+
InstanceRequest,
|
|
27
|
+
InstanceRecord,
|
|
28
|
+
Environment as EnvironmentModel,
|
|
29
|
+
VerifiersCheckResponse,
|
|
30
|
+
VerificationResponse,
|
|
31
|
+
VerifiersExecuteResponse,
|
|
32
|
+
)
|
|
24
33
|
|
|
25
34
|
from .instance import (
|
|
26
35
|
AsyncInstanceClient,
|
|
27
36
|
ResetRequest,
|
|
28
37
|
ResetResponse,
|
|
29
|
-
ValidatorType,
|
|
30
38
|
ExecuteFunctionResponse,
|
|
31
39
|
)
|
|
32
|
-
from
|
|
40
|
+
from ..config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, REGION_BASE_URL
|
|
33
41
|
from .instance.base import default_httpx_client
|
|
42
|
+
from .instance.client import ValidatorType
|
|
34
43
|
from .resources.base import Resource
|
|
35
44
|
from .resources.sqlite import AsyncSQLiteResource
|
|
36
45
|
from .resources.browser import AsyncBrowserResource
|
|
@@ -38,8 +47,8 @@ from .resources.browser import AsyncBrowserResource
|
|
|
38
47
|
logger = logging.getLogger(__name__)
|
|
39
48
|
|
|
40
49
|
|
|
41
|
-
class
|
|
42
|
-
def __init__(self, client: AsyncWrapper, **kwargs):
|
|
50
|
+
class AsyncEnv(EnvironmentBase):
|
|
51
|
+
def __init__(self, client: Optional[AsyncWrapper], **kwargs):
|
|
43
52
|
super().__init__(**kwargs)
|
|
44
53
|
self._client = client
|
|
45
54
|
self._instance: Optional[AsyncInstanceClient] = None
|
|
@@ -48,10 +57,16 @@ class AsyncEnvironment(EnvironmentBase):
|
|
|
48
57
|
def instance(self) -> AsyncInstanceClient:
|
|
49
58
|
if self._instance is None:
|
|
50
59
|
self._instance = AsyncInstanceClient(
|
|
51
|
-
self.manager_url, self._client.httpx_client
|
|
60
|
+
self.manager_url, self._client.httpx_client if self._client else None
|
|
52
61
|
)
|
|
53
62
|
return self._instance
|
|
54
63
|
|
|
64
|
+
@property
|
|
65
|
+
def _load_client(self) -> AsyncWrapper:
|
|
66
|
+
if self._client is None:
|
|
67
|
+
raise ValueError("Client not initialized")
|
|
68
|
+
return self._client
|
|
69
|
+
|
|
55
70
|
async def reset(
|
|
56
71
|
self, seed: Optional[int] = None, timestamp: Optional[int] = None
|
|
57
72
|
) -> ResetResponse:
|
|
@@ -70,10 +85,7 @@ class AsyncEnvironment(EnvironmentBase):
|
|
|
70
85
|
return await self.instance.resources()
|
|
71
86
|
|
|
72
87
|
async def close(self) -> InstanceRecord:
|
|
73
|
-
|
|
74
|
-
"DELETE", f"/v1/env/instances/{self.instance_id}"
|
|
75
|
-
)
|
|
76
|
-
return InstanceRecord(**response.json())
|
|
88
|
+
return await _delete_instance(self._load_client, self.instance_id)
|
|
77
89
|
|
|
78
90
|
async def verify(self, validator: ValidatorType) -> ExecuteFunctionResponse:
|
|
79
91
|
return await self.instance.verify(validator)
|
|
@@ -83,6 +95,41 @@ class AsyncEnvironment(EnvironmentBase):
|
|
|
83
95
|
) -> ExecuteFunctionResponse:
|
|
84
96
|
return await self.instance.verify_raw(function_code, function_name)
|
|
85
97
|
|
|
98
|
+
async def check_bundle_exists(self, bundle_hash: str) -> VerifiersCheckResponse:
|
|
99
|
+
return await _check_bundle_exists(self._load_client, bundle_hash)
|
|
100
|
+
|
|
101
|
+
async def execute_verifier_remote(
|
|
102
|
+
self,
|
|
103
|
+
bundle_data: bytes,
|
|
104
|
+
bundle_sha: str,
|
|
105
|
+
key: str,
|
|
106
|
+
function_name: str,
|
|
107
|
+
args: tuple,
|
|
108
|
+
kwargs: dict,
|
|
109
|
+
timeout: Optional[int] = 30,
|
|
110
|
+
needs_upload: bool = True,
|
|
111
|
+
) -> VerifiersExecuteResponse:
|
|
112
|
+
return await _execute_verifier_remote(
|
|
113
|
+
self._load_client,
|
|
114
|
+
bundle_data,
|
|
115
|
+
bundle_sha,
|
|
116
|
+
key,
|
|
117
|
+
function_name,
|
|
118
|
+
args,
|
|
119
|
+
kwargs,
|
|
120
|
+
timeout,
|
|
121
|
+
needs_upload
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def __getstate__(self):
|
|
125
|
+
state = self.__dict__.copy()
|
|
126
|
+
state.pop("_client", None)
|
|
127
|
+
state.pop("_instance", None)
|
|
128
|
+
return state
|
|
129
|
+
|
|
130
|
+
def __setstate__(self, state):
|
|
131
|
+
self.__dict__.update(state)
|
|
132
|
+
|
|
86
133
|
|
|
87
134
|
class AsyncFleet:
|
|
88
135
|
def __init__(
|
|
@@ -91,8 +138,9 @@ class AsyncFleet:
|
|
|
91
138
|
base_url: Optional[str] = None,
|
|
92
139
|
httpx_client: Optional[httpx.AsyncClient] = None,
|
|
93
140
|
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
141
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
94
142
|
):
|
|
95
|
-
self._httpx_client = httpx_client or default_httpx_client(max_retries)
|
|
143
|
+
self._httpx_client = httpx_client or default_httpx_client(max_retries, timeout)
|
|
96
144
|
self.client = AsyncWrapper(
|
|
97
145
|
api_key=api_key,
|
|
98
146
|
base_url=base_url,
|
|
@@ -113,7 +161,7 @@ class AsyncFleet:
|
|
|
113
161
|
|
|
114
162
|
async def make(
|
|
115
163
|
self, env_key: str, region: Optional[str] = None
|
|
116
|
-
) ->
|
|
164
|
+
) -> AsyncEnv:
|
|
117
165
|
if ":" in env_key:
|
|
118
166
|
env_key_part, version = env_key.split(":", 1)
|
|
119
167
|
if not version.startswith("v"):
|
|
@@ -130,13 +178,13 @@ class AsyncFleet:
|
|
|
130
178
|
json=request.model_dump(),
|
|
131
179
|
base_url=region_base_url,
|
|
132
180
|
)
|
|
133
|
-
instance =
|
|
181
|
+
instance = AsyncEnv(client=self.client, **response.json())
|
|
134
182
|
await instance.instance.load()
|
|
135
183
|
return instance
|
|
136
184
|
|
|
137
185
|
async def instances(
|
|
138
186
|
self, status: Optional[str] = None, region: Optional[str] = None
|
|
139
|
-
) -> List[
|
|
187
|
+
) -> List[AsyncEnv]:
|
|
140
188
|
params = {}
|
|
141
189
|
if status:
|
|
142
190
|
params["status"] = status
|
|
@@ -145,18 +193,88 @@ class AsyncFleet:
|
|
|
145
193
|
|
|
146
194
|
response = await self.client.request("GET", "/v1/env/instances", params=params)
|
|
147
195
|
return [
|
|
148
|
-
|
|
196
|
+
AsyncEnv(client=self.client, **instance_data)
|
|
149
197
|
for instance_data in response.json()
|
|
150
198
|
]
|
|
151
199
|
|
|
152
|
-
async def instance(self, instance_id: str) ->
|
|
200
|
+
async def instance(self, instance_id: str) -> AsyncEnv:
|
|
153
201
|
response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
|
|
154
|
-
instance =
|
|
202
|
+
instance = AsyncEnv(client=self.client, **response.json())
|
|
155
203
|
await instance.instance.load()
|
|
156
204
|
return instance
|
|
157
205
|
|
|
158
|
-
async def
|
|
159
|
-
|
|
160
|
-
|
|
206
|
+
async def check_bundle_exists(self, bundle_hash: str) -> VerifiersCheckResponse:
|
|
207
|
+
return await _check_bundle_exists(self.client, bundle_hash)
|
|
208
|
+
|
|
209
|
+
async def execute_verifier_remote(
|
|
210
|
+
self, bundle_data: bytes, args: tuple, kwargs: dict, timeout: Optional[int] = 30
|
|
211
|
+
) -> VerifiersExecuteResponse:
|
|
212
|
+
return await _execute_verifier_remote(
|
|
213
|
+
self.client, bundle_data, args, kwargs, timeout
|
|
161
214
|
)
|
|
162
|
-
|
|
215
|
+
|
|
216
|
+
async def delete(self, instance_id: str) -> InstanceRecord:
|
|
217
|
+
return await _delete_instance(self.client, instance_id)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Shared
|
|
221
|
+
async def _delete_instance(client: AsyncWrapper, instance_id: str) -> InstanceRecord:
|
|
222
|
+
response = await client.request("DELETE", f"/v1/env/instances/{instance_id}")
|
|
223
|
+
return InstanceRecord(**response.json())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def _check_bundle_exists(
|
|
227
|
+
client: AsyncWrapper, bundle_hash: str
|
|
228
|
+
) -> VerifiersCheckResponse:
|
|
229
|
+
response = await client.request("GET", f"/v1/verifiers/check?sha256={bundle_hash}")
|
|
230
|
+
return VerifiersCheckResponse(**response.json())
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def _execute_verifier_remote(
|
|
234
|
+
client: AsyncWrapper,
|
|
235
|
+
bundle_data: bytes,
|
|
236
|
+
bundle_sha: str,
|
|
237
|
+
key: str,
|
|
238
|
+
function_name: str,
|
|
239
|
+
args: tuple,
|
|
240
|
+
kwargs: dict,
|
|
241
|
+
timeout: Optional[int] = 30,
|
|
242
|
+
needs_upload: bool = True,
|
|
243
|
+
) -> VerificationResponse:
|
|
244
|
+
# Pickle args and kwargs together
|
|
245
|
+
# The first arg should be None as a placeholder for env
|
|
246
|
+
args_with_none = (None,) + args
|
|
247
|
+
args_kwargs_pickled = cloudpickle.dumps({"args": args_with_none, "kwargs": kwargs})
|
|
248
|
+
args_kwargs_b64 = base64.b64encode(args_kwargs_pickled).decode("utf-8")
|
|
249
|
+
|
|
250
|
+
# Build request data
|
|
251
|
+
request_data = {
|
|
252
|
+
"key": key,
|
|
253
|
+
"sha256": bundle_sha,
|
|
254
|
+
"args": args_kwargs_b64,
|
|
255
|
+
"function_name": function_name,
|
|
256
|
+
"timeout": timeout,
|
|
257
|
+
"region": "us-west-1", # TODO: make configurable
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
# Add bundle data only if upload is needed
|
|
261
|
+
if needs_upload:
|
|
262
|
+
bundle_b64 = base64.b64encode(bundle_data).decode("utf-8")
|
|
263
|
+
request_data["bundle"] = bundle_b64
|
|
264
|
+
|
|
265
|
+
# Debug logging
|
|
266
|
+
logger.debug(f"Sending verifier execute request: key={key}, sha256={bundle_sha[:8]}..., function_name={function_name}")
|
|
267
|
+
logger.debug(f"Request has bundle: {needs_upload}")
|
|
268
|
+
logger.debug(f"Using client with base_url: {client.base_url}")
|
|
269
|
+
logger.debug(f"Request data keys: {list(request_data.keys())}")
|
|
270
|
+
logger.debug(f"Bundle size: {len(request_data.get('bundle', ''))} chars" if 'bundle' in request_data else "No bundle")
|
|
271
|
+
|
|
272
|
+
# Note: This should be called on the instance URL, not the orchestrator
|
|
273
|
+
# The instance has manager URLs for verifier execution
|
|
274
|
+
response = await client.request("POST", "/v1/verifiers/execute", json=request_data)
|
|
275
|
+
|
|
276
|
+
# Debug the response
|
|
277
|
+
response_json = response.json()
|
|
278
|
+
logger.debug(f"Verifier execute response: {response_json}")
|
|
279
|
+
|
|
280
|
+
return VerifiersExecuteResponse(**response_json)
|
fleet/_async/env/client.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from ..client import AsyncFleet,
|
|
2
|
-
from
|
|
1
|
+
from ..client import AsyncFleet, AsyncEnv
|
|
2
|
+
from ...models import Environment as EnvironmentModel
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
async def make_async(env_key: str, region: Optional[str] = None) ->
|
|
6
|
+
async def make_async(env_key: str, region: Optional[str] = None) -> AsyncEnv:
|
|
7
7
|
return await AsyncFleet().make(env_key, region=region)
|
|
8
8
|
|
|
9
9
|
|
|
@@ -17,9 +17,9 @@ async def list_regions_async() -> List[str]:
|
|
|
17
17
|
|
|
18
18
|
async def list_instances_async(
|
|
19
19
|
status: Optional[str] = None, region: Optional[str] = None
|
|
20
|
-
) -> List[
|
|
20
|
+
) -> List[AsyncEnv]:
|
|
21
21
|
return await AsyncFleet().instances(status=status, region=region)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
async def get_async(instance_id: str) ->
|
|
24
|
+
async def get_async(instance_id: str) -> AsyncEnv:
|
|
25
25
|
return await AsyncFleet().instance(instance_id)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Fleet SDK Environment Module."""
|
|
2
2
|
|
|
3
3
|
from .client import AsyncInstanceClient, ValidatorType
|
|
4
|
-
from .models import (
|
|
4
|
+
from ...instance.models import (
|
|
5
5
|
ResetRequest,
|
|
6
6
|
ResetResponse,
|
|
7
7
|
CDPDescribeResponse,
|
|
@@ -12,7 +12,6 @@ from .models import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
"ValidatorType",
|
|
16
15
|
"AsyncInstanceClient",
|
|
17
16
|
"ResetRequest",
|
|
18
17
|
"ResetResponse",
|
|
@@ -20,5 +19,5 @@ __all__ = [
|
|
|
20
19
|
"ChromeStartRequest",
|
|
21
20
|
"ChromeStartResponse",
|
|
22
21
|
"ChromeStatusResponse",
|
|
23
|
-
"ExecuteFunctionResponse"
|
|
22
|
+
"ExecuteFunctionResponse",
|
|
24
23
|
]
|
fleet/_async/instance/base.py
CHANGED
|
@@ -3,7 +3,10 @@ import httpx_retries
|
|
|
3
3
|
from typing import Dict, Any, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def default_httpx_client(max_retries: int) -> httpx.AsyncClient:
|
|
6
|
+
def default_httpx_client(max_retries: int, timeout: float) -> httpx.AsyncClient:
|
|
7
|
+
if max_retries <= 0:
|
|
8
|
+
return httpx.AsyncClient(timeout=timeout)
|
|
9
|
+
|
|
7
10
|
policy = httpx_retries.Retry(
|
|
8
11
|
total=max_retries,
|
|
9
12
|
status_forcelist=[
|
|
@@ -21,7 +24,7 @@ def default_httpx_client(max_retries: int) -> httpx.AsyncClient:
|
|
|
21
24
|
transport=httpx.AsyncHTTPTransport(retries=2), retry=policy
|
|
22
25
|
)
|
|
23
26
|
return httpx.AsyncClient(
|
|
24
|
-
timeout=
|
|
27
|
+
timeout=timeout,
|
|
25
28
|
transport=retry,
|
|
26
29
|
)
|
|
27
30
|
|
fleet/_async/instance/client.py
CHANGED
|
@@ -14,10 +14,10 @@ from ..resources.base import Resource
|
|
|
14
14
|
from fleet.verifiers import DatabaseSnapshot
|
|
15
15
|
|
|
16
16
|
from ..exceptions import FleetEnvironmentError
|
|
17
|
-
from
|
|
17
|
+
from ...config import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT
|
|
18
18
|
|
|
19
19
|
from .base import AsyncWrapper, default_httpx_client
|
|
20
|
-
from .models import (
|
|
20
|
+
from ...instance.models import (
|
|
21
21
|
ResetRequest,
|
|
22
22
|
ResetResponse,
|
|
23
23
|
Resource as ResourceModel,
|
|
@@ -51,7 +51,8 @@ class AsyncInstanceClient:
|
|
|
51
51
|
self.base_url = url
|
|
52
52
|
self.client = AsyncWrapper(
|
|
53
53
|
url=self.base_url,
|
|
54
|
-
httpx_client=httpx_client
|
|
54
|
+
httpx_client=httpx_client
|
|
55
|
+
or default_httpx_client(DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT),
|
|
55
56
|
)
|
|
56
57
|
self._resources: Optional[List[ResourceModel]] = None
|
|
57
58
|
self._resources_state: Dict[str, Dict[str, Resource]] = {
|
|
@@ -136,7 +137,7 @@ class AsyncInstanceClient:
|
|
|
136
137
|
|
|
137
138
|
self._resources = [ResourceModel(**resource) for resource in resources_list]
|
|
138
139
|
for resource in self._resources:
|
|
139
|
-
if resource.type not in self._resources_state:
|
|
140
|
+
if resource.type.value not in self._resources_state:
|
|
140
141
|
self._resources_state[resource.type.value] = {}
|
|
141
142
|
self._resources_state[resource.type.value][resource.name] = (
|
|
142
143
|
RESOURCE_TYPES[resource.type](resource, self.client)
|
fleet/_async/playwright.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
from typing import List, Dict, Any
|
|
3
3
|
from playwright.async_api import async_playwright, Browser, Page
|
|
4
|
-
from .client import
|
|
4
|
+
from .client import AsyncEnv
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Key mapping for computer use actions
|
|
@@ -65,7 +65,7 @@ class AsyncFleetPlaywrightWrapper:
|
|
|
65
65
|
|
|
66
66
|
def __init__(
|
|
67
67
|
self,
|
|
68
|
-
env:
|
|
68
|
+
env: AsyncEnv,
|
|
69
69
|
display_width: int = 1920,
|
|
70
70
|
display_height: int = 1080,
|
|
71
71
|
):
|
fleet/_async/resources/base.py
CHANGED