fleet-python 0.2.42__py3-none-any.whl → 0.2.44__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.
- fleet/__init__.py +9 -7
- fleet/_async/__init__.py +28 -16
- fleet/_async/client.py +161 -69
- fleet/_async/env/client.py +9 -2
- fleet/_async/instance/client.py +1 -1
- fleet/_async/resources/sqlite.py +34 -34
- fleet/_async/tasks.py +42 -41
- fleet/_async/verifiers/verifier.py +3 -3
- fleet/client.py +164 -61
- fleet/env/client.py +9 -2
- fleet/instance/client.py +2 -4
- fleet/models.py +3 -1
- fleet/resources/sqlite.py +37 -43
- fleet/tasks.py +49 -65
- fleet/verifiers/__init__.py +1 -1
- fleet/verifiers/db.py +41 -36
- fleet/verifiers/parse.py +4 -1
- fleet/verifiers/sql_differ.py +8 -8
- fleet/verifiers/verifier.py +19 -7
- {fleet_python-0.2.42.dist-info → fleet_python-0.2.44.dist-info}/METADATA +1 -1
- {fleet_python-0.2.42.dist-info → fleet_python-0.2.44.dist-info}/RECORD +24 -24
- {fleet_python-0.2.42.dist-info → fleet_python-0.2.44.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.42.dist-info → fleet_python-0.2.44.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.42.dist-info → fleet_python-0.2.44.dist-info}/top_level.txt +0 -0
fleet/_async/resources/sqlite.py
CHANGED
|
@@ -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
|
|
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:
|
|
33
|
-
self._schemas:
|
|
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) ->
|
|
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
|
|
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:
|
|
99
|
-
self._conditions:
|
|
100
|
-
self._limit: int
|
|
101
|
-
self._order_by: str
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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
|
|
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:
|
|
193
|
+
self._cached: Optional[Dict[str, Any]] = None
|
|
194
194
|
|
|
195
|
-
async def _get_primary_key_columns(self, table: 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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
444
|
-
self._conditions:
|
|
445
|
-
self._joins:
|
|
446
|
-
self._limit: int
|
|
447
|
-
self._order_by: str
|
|
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:
|
|
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:
|
|
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:
|
|
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) ->
|
|
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:
|
|
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) ->
|
|
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) ->
|
|
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
|
|
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
|
|
686
|
+
ignore_config: Optional[IgnoreConfig] = None,
|
|
687
687
|
) -> AsyncSnapshotDiff:
|
|
688
688
|
"""Compare this database with another AsyncSQLiteResource.
|
|
689
689
|
|
fleet/_async/tasks.py
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import re
|
|
6
5
|
from datetime import datetime
|
|
7
6
|
from typing import Any, Dict, Optional, List
|
|
8
|
-
from uuid import UUID
|
|
9
7
|
|
|
10
8
|
from pydantic import BaseModel, Field, validator
|
|
11
9
|
|
|
@@ -46,7 +44,7 @@ class Task(BaseModel):
|
|
|
46
44
|
@property
|
|
47
45
|
def env_key(self) -> str:
|
|
48
46
|
"""Get the environment key combining env_id and version."""
|
|
49
|
-
if self.version:
|
|
47
|
+
if self.version and self.version != "None" and ":" not in self.env_id:
|
|
50
48
|
return f"{self.env_id}:{self.version}"
|
|
51
49
|
return self.env_id
|
|
52
50
|
|
|
@@ -75,7 +73,7 @@ class Task(BaseModel):
|
|
|
75
73
|
if inspect.iscoroutine(result):
|
|
76
74
|
# Check if we're already in an event loop
|
|
77
75
|
try:
|
|
78
|
-
|
|
76
|
+
asyncio.get_running_loop()
|
|
79
77
|
# We're in an async context, can't use asyncio.run()
|
|
80
78
|
raise RuntimeError(
|
|
81
79
|
"Cannot run async verifier in sync mode while event loop is running. "
|
|
@@ -121,67 +119,78 @@ class Task(BaseModel):
|
|
|
121
119
|
|
|
122
120
|
|
|
123
121
|
def verifier_from_string(
|
|
124
|
-
verifier_func: str,
|
|
125
|
-
|
|
126
|
-
verifier_key: str,
|
|
127
|
-
sha256: str = ""
|
|
128
|
-
) -> 'VerifierFunction':
|
|
122
|
+
verifier_func: str, verifier_id: str, verifier_key: str, sha256: str = ""
|
|
123
|
+
) -> "VerifierFunction":
|
|
129
124
|
"""Create a verifier function from string code.
|
|
130
|
-
|
|
125
|
+
|
|
131
126
|
Args:
|
|
132
127
|
verifier_func: The verifier function code as a string
|
|
133
128
|
verifier_id: Unique identifier for the verifier
|
|
134
129
|
verifier_key: Key/name for the verifier
|
|
135
130
|
sha256: SHA256 hash of the verifier code
|
|
136
|
-
|
|
131
|
+
|
|
137
132
|
Returns:
|
|
138
133
|
VerifierFunction instance that can be used to verify tasks
|
|
139
134
|
"""
|
|
140
135
|
try:
|
|
141
136
|
import inspect
|
|
142
|
-
from .verifiers import
|
|
137
|
+
from .verifiers.verifier import AsyncVerifierFunction
|
|
143
138
|
from fleet.verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
144
139
|
from fleet.verifiers.db import IgnoreConfig
|
|
145
|
-
|
|
140
|
+
|
|
146
141
|
# Create a local namespace for executing the code
|
|
147
142
|
local_namespace = {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
"TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
|
|
144
|
+
"TASK_FAILED_SCORE": TASK_FAILED_SCORE,
|
|
145
|
+
"IgnoreConfig": IgnoreConfig,
|
|
146
|
+
"Environment": object, # Add Environment type if needed
|
|
152
147
|
}
|
|
153
|
-
|
|
148
|
+
|
|
154
149
|
# Execute the verifier code in the namespace
|
|
155
150
|
exec(verifier_func, globals(), local_namespace)
|
|
156
|
-
|
|
151
|
+
|
|
157
152
|
# Find the function that was defined
|
|
158
153
|
func_obj = None
|
|
159
154
|
for name, obj in local_namespace.items():
|
|
160
155
|
if inspect.isfunction(obj):
|
|
161
156
|
func_obj = obj
|
|
162
157
|
break
|
|
163
|
-
|
|
158
|
+
|
|
164
159
|
if func_obj is None:
|
|
165
160
|
raise ValueError("No function found in verifier code")
|
|
166
|
-
|
|
167
|
-
# Create an AsyncVerifierFunction instance
|
|
168
|
-
verifier_instance = AsyncVerifierFunction(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
161
|
+
|
|
162
|
+
# Create an AsyncVerifierFunction instance with raw code
|
|
163
|
+
verifier_instance = AsyncVerifierFunction(
|
|
164
|
+
func_obj,
|
|
165
|
+
verifier_key,
|
|
166
|
+
verifier_id=verifier_id,
|
|
167
|
+
sha256=sha256,
|
|
168
|
+
raw_code=verifier_func,
|
|
169
|
+
)
|
|
170
|
+
|
|
174
171
|
return verifier_instance
|
|
175
|
-
|
|
172
|
+
|
|
176
173
|
except Exception as e:
|
|
177
174
|
raise ValueError(f"Failed to create verifier from string: {e}")
|
|
178
175
|
|
|
179
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
|
+
|
|
180
189
|
async def load_tasks(
|
|
181
190
|
env_key: Optional[str] = None,
|
|
182
191
|
keys: Optional[List[str]] = None,
|
|
183
192
|
version: Optional[str] = None,
|
|
184
|
-
team_id: Optional[str] = None
|
|
193
|
+
team_id: Optional[str] = None,
|
|
185
194
|
) -> List[Task]:
|
|
186
195
|
"""Convenience function to load tasks with optional filtering.
|
|
187
196
|
|
|
@@ -201,17 +210,12 @@ async def load_tasks(
|
|
|
201
210
|
|
|
202
211
|
client = get_client()
|
|
203
212
|
return await client.load_tasks(
|
|
204
|
-
env_key=env_key,
|
|
205
|
-
keys=keys,
|
|
206
|
-
version=version,
|
|
207
|
-
team_id=team_id
|
|
213
|
+
env_key=env_key, keys=keys, version=version, team_id=team_id
|
|
208
214
|
)
|
|
209
215
|
|
|
210
216
|
|
|
211
217
|
async def update_task(
|
|
212
|
-
task_key: str,
|
|
213
|
-
prompt: Optional[str] = None,
|
|
214
|
-
verifier_code: Optional[str] = None
|
|
218
|
+
task_key: str, prompt: Optional[str] = None, verifier_code: Optional[str] = None
|
|
215
219
|
):
|
|
216
220
|
"""Convenience function to update an existing task.
|
|
217
221
|
|
|
@@ -228,11 +232,8 @@ async def update_task(
|
|
|
228
232
|
response = await fleet.update_task("my-task", verifier_code="def verify(env): return True")
|
|
229
233
|
"""
|
|
230
234
|
from .global_client import get_client
|
|
231
|
-
from ..models import TaskResponse
|
|
232
235
|
|
|
233
236
|
client = get_client()
|
|
234
237
|
return await client.update_task(
|
|
235
|
-
task_key=task_key,
|
|
236
|
-
prompt=prompt,
|
|
237
|
-
verifier_code=verifier_code
|
|
238
|
+
task_key=task_key, prompt=prompt, verifier_code=verifier_code
|
|
238
239
|
)
|
|
@@ -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) ->
|
|
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) ->
|
|
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
|
|