fleet-python 0.2.43__py3-none-any.whl → 0.2.45__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/client.py CHANGED
@@ -130,7 +130,7 @@ class SyncEnv(EnvironmentBase):
130
130
  return self.instance.verify(validator)
131
131
 
132
132
  def verify_raw(
133
- self, function_code: str, function_name: str | None = None
133
+ self, function_code: str, function_name: Optional[str] = None
134
134
  ) -> ExecuteFunctionResponse:
135
135
  return self.instance.verify_raw(function_code, function_name)
136
136
 
@@ -302,10 +302,19 @@ class Fleet:
302
302
 
303
303
  return self.load_task_array_from_string(tasks_data)
304
304
 
305
- def load_task_array_from_string(self, serialized_tasks: List[Dict]) -> List[Task]:
305
+ def load_task_array_from_string(self, serialized_tasks: str) -> List[Task]:
306
306
  tasks = []
307
307
 
308
- json_tasks = json.loads(serialized_tasks)
308
+ parsed_data = json.loads(serialized_tasks)
309
+ if isinstance(parsed_data, list):
310
+ json_tasks = parsed_data
311
+ elif isinstance(parsed_data, dict) and "tasks" in parsed_data:
312
+ json_tasks = parsed_data["tasks"]
313
+ else:
314
+ raise ValueError(
315
+ "Invalid JSON structure: expected array or object with 'tasks' key"
316
+ )
317
+
309
318
  for json_task in json_tasks:
310
319
  parsed_task = self.load_task_from_json(json_task)
311
320
  tasks.append(parsed_task)
@@ -316,25 +325,47 @@ class Fleet:
316
325
  return self.load_task_from_json(task_json)
317
326
 
318
327
  def load_task_from_json(self, task_json: Dict) -> Task:
328
+ verifier = None
329
+ verifier_code = task_json.get("verifier_func") or task_json.get("verifier_code")
330
+
331
+ # Try to find verifier_id in multiple locations
332
+ verifier_id = task_json.get("verifier_id")
333
+ if (
334
+ not verifier_id
335
+ and "metadata" in task_json
336
+ and isinstance(task_json["metadata"], dict)
337
+ ):
338
+ verifier_metadata = task_json["metadata"].get("verifier", {})
339
+ if isinstance(verifier_metadata, dict):
340
+ verifier_id = verifier_metadata.get("verifier_id")
341
+
342
+ # If no verifier_id found, use the task key/id as fallback
343
+ if not verifier_id:
344
+ verifier_id = task_json.get("key", task_json.get("id"))
345
+
319
346
  try:
320
- if "verifier_id" in task_json and task_json["verifier_id"]:
347
+ if verifier_id and verifier_code:
321
348
  verifier = self._create_verifier_from_data(
322
- verifier_id=task_json["verifier_id"],
323
- verifier_key=task_json["key"],
324
- verifier_code=task_json["verifier_func"],
349
+ verifier_id=verifier_id,
350
+ verifier_key=task_json.get("key", task_json.get("id")),
351
+ verifier_code=verifier_code,
325
352
  verifier_sha=task_json.get("verifier_sha", ""),
326
353
  )
327
354
  except Exception as e:
328
- logger.warning(f"Failed to create verifier {task_json['key']}: {e}")
355
+ logger.warning(
356
+ f"Failed to create verifier {task_json.get('key', task_json.get('id'))}: {e}"
357
+ )
329
358
 
330
359
  task = Task(
331
- key=task_json["key"],
360
+ key=task_json.get("key", task_json.get("id")),
332
361
  prompt=task_json["prompt"],
333
- env_id=task_json["env_id"], # Use env_id from the data
334
- created_at=task_json["created_at"],
362
+ env_id=task_json.get(
363
+ "env_id", task_json.get("env_key")
364
+ ), # Use env_id or fallback to env_key
365
+ created_at=task_json.get("created_at"),
335
366
  version=task_json.get("version"),
336
367
  env_variables=task_json.get("env_variables", {}),
337
- verifier_func=task_json.get("verifier_func"), # Set verifier code
368
+ verifier_func=verifier_code, # Set verifier code
338
369
  verifier=verifier, # Use created verifier or None
339
370
  metadata=task_json.get("metadata", {}), # Default empty metadata
340
371
  )
@@ -636,7 +667,7 @@ class Fleet:
636
667
  AsyncVerifierFunction created from the verifier code
637
668
  """
638
669
  # Fetch verifier from API
639
- response = self.client.request("GET", f"/v1/verifier/{verifier_id}")
670
+ response = self.client.request("GET", f"/v1/verifiers/{verifier_id}")
640
671
  verifier_data = response.json()
641
672
 
642
673
  # Use the common method to create verifier
fleet/instance/client.py CHANGED
@@ -63,9 +63,7 @@ class InstanceClient:
63
63
  def load(self) -> None:
64
64
  self._load_resources()
65
65
 
66
- def reset(
67
- self, reset_request: Optional[ResetRequest] = None
68
- ) -> ResetResponse:
66
+ def reset(self, reset_request: Optional[ResetRequest] = None) -> ResetResponse:
69
67
  response = self.client.request(
70
68
  "POST", "/reset", json=reset_request.model_dump() if reset_request else None
71
69
  )
@@ -108,7 +106,7 @@ class InstanceClient:
108
106
  return self.verify_raw(function_code, function_name)
109
107
 
110
108
  def verify_raw(
111
- self, function_code: str, function_name: str | None = None
109
+ self, function_code: str, function_name: Optional[str] = None
112
110
  ) -> ExecuteFunctionResponse:
113
111
  try:
114
112
  function_code = convert_verifier_string(function_code)
fleet/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 SyncDatabaseSnapshot:
26
26
  """Async database snapshot that fetches data through API and stores locally for diffing."""
27
27
 
28
- def __init__(self, resource: "SQLiteResource", name: str | None = None):
28
+ def __init__(self, resource: "SQLiteResource", name: Optional[str] = None):
29
29
  self.resource = resource
30
30
  self.name = name or f"snapshot_{datetime.utcnow().isoformat()}"
31
31
  self.created_at = datetime.utcnow()
32
- self._data: dict[str, list[dict[str, Any]]] = {}
33
- self._schemas: dict[str, list[str]] = {}
32
+ self._data: Dict[str, List[Dict[str, Any]]] = {}
33
+ self._schemas: Dict[str, List[str]] = {}
34
34
  self._fetched = False
35
35
 
36
36
  def _ensure_fetched(self):
@@ -69,7 +69,7 @@ class SyncDatabaseSnapshot:
69
69
 
70
70
  self._fetched = True
71
71
 
72
- def tables(self) -> list[str]:
72
+ def tables(self) -> List[str]:
73
73
  """Get list of all tables in the snapshot."""
74
74
  self._ensure_fetched()
75
75
  return list(self._data.keys())
@@ -81,7 +81,7 @@ class SyncDatabaseSnapshot:
81
81
  def diff(
82
82
  self,
83
83
  other: "SyncDatabaseSnapshot",
84
- ignore_config: IgnoreConfig | None = None,
84
+ ignore_config: Optional[IgnoreConfig] = None,
85
85
  ) -> "SyncSnapshotDiff":
86
86
  """Compare this snapshot with another."""
87
87
  self._ensure_fetched()
@@ -95,13 +95,13 @@ class SyncSnapshotQueryBuilder:
95
95
  def __init__(self, snapshot: SyncDatabaseSnapshot, table: str):
96
96
  self._snapshot = snapshot
97
97
  self._table = table
98
- self._select_cols: list[str] = ["*"]
99
- self._conditions: list[tuple[str, str, Any]] = []
100
- self._limit: int | None = None
101
- self._order_by: str | None = None
98
+ self._select_cols: List[str] = ["*"]
99
+ self._conditions: List[Tuple[str, str, Any]] = []
100
+ self._limit: Optional[int] = None
101
+ self._order_by: Optional[str] = None
102
102
  self._order_desc: bool = False
103
103
 
104
- def _get_data(self) -> list[dict[str, Any]]:
104
+ def _get_data(self) -> List[Dict[str, Any]]:
105
105
  """Get table data from snapshot."""
106
106
  self._snapshot._ensure_fetched()
107
107
  return self._snapshot._data.get(self._table, [])
@@ -122,11 +122,11 @@ class SyncSnapshotQueryBuilder:
122
122
  qb._order_desc = desc
123
123
  return qb
124
124
 
125
- def first(self) -> dict[str, Any] | None:
125
+ def first(self) -> Optional[Dict[str, Any]]:
126
126
  rows = self.all()
127
127
  return rows[0] if rows else None
128
128
 
129
- def all(self) -> list[dict[str, Any]]:
129
+ def all(self) -> List[Dict[str, Any]]:
130
130
  data = self._get_data()
131
131
 
132
132
  # Apply filters
@@ -185,14 +185,14 @@ class SyncSnapshotDiff:
185
185
  self,
186
186
  before: SyncDatabaseSnapshot,
187
187
  after: SyncDatabaseSnapshot,
188
- ignore_config: IgnoreConfig | None = None,
188
+ ignore_config: Optional[IgnoreConfig] = None,
189
189
  ):
190
190
  self.before = before
191
191
  self.after = after
192
192
  self.ignore_config = ignore_config or IgnoreConfig()
193
- self._cached: dict[str, Any] | None = None
193
+ self._cached: Optional[Dict[str, Any]] = None
194
194
 
195
- def _get_primary_key_columns(self, table: str) -> list[str]:
195
+ 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 = self.after.resource.query(f"PRAGMA table_info({table})")
@@ -222,7 +222,7 @@ class SyncSnapshotDiff:
222
222
  return self._cached
223
223
 
224
224
  all_tables = set(self.before.tables()) | set(self.after.tables())
225
- diff: dict[str, dict[str, Any]] = {}
225
+ diff: Dict[str, Dict[str, Any]] = {}
226
226
 
227
227
  for tbl in all_tables:
228
228
  if self.ignore_config.should_ignore_table(tbl):
@@ -236,7 +236,7 @@ class SyncSnapshotDiff:
236
236
  after_data = self.after._data.get(tbl, [])
237
237
 
238
238
  # Create indexes by primary key
239
- def make_key(row: dict, pk_cols: list[str]) -> Any:
239
+ def make_key(row: dict, pk_cols: List[str]) -> Any:
240
240
  if len(pk_cols) == 1:
241
241
  return row.get(pk_cols[0])
242
242
  return tuple(row.get(col) for col in pk_cols)
@@ -304,12 +304,12 @@ class SyncSnapshotDiff:
304
304
  self._cached = diff
305
305
  return diff
306
306
 
307
- def expect_only(self, allowed_changes: list[dict[str, Any]]):
307
+ def expect_only(self, allowed_changes: List[Dict[str, Any]]):
308
308
  """Ensure only specified changes occurred."""
309
309
  diff = self._collect()
310
310
 
311
311
  def _is_change_allowed(
312
- table: str, row_id: Any, field: str | None, after_value: Any
312
+ table: str, row_id: Any, field: Optional[str], after_value: Any
313
313
  ) -> bool:
314
314
  """Check if a change is in the allowed list using semantic comparison."""
315
315
  for allowed in allowed_changes:
@@ -440,11 +440,11 @@ class SyncQueryBuilder:
440
440
  def __init__(self, resource: "SQLiteResource", table: str):
441
441
  self._resource = resource
442
442
  self._table = table
443
- self._select_cols: list[str] = ["*"]
444
- self._conditions: list[tuple[str, str, Any]] = []
445
- self._joins: list[tuple[str, dict[str, str]]] = []
446
- self._limit: int | None = None
447
- self._order_by: str | None = None
443
+ self._select_cols: List[str] = ["*"]
444
+ self._conditions: List[Tuple[str, str, Any]] = []
445
+ self._joins: List[Tuple[str, Dict[str, str]]] = []
446
+ self._limit: Optional[int] = None
447
+ self._order_by: Optional[str] = None
448
448
 
449
449
  # Column projection / limiting / ordering
450
450
  def select(self, *columns: str) -> "SyncQueryBuilder":
@@ -486,12 +486,12 @@ class SyncQueryBuilder:
486
486
  def lte(self, column: str, value: Any) -> "SyncQueryBuilder":
487
487
  return self._add_condition(column, "<=", value)
488
488
 
489
- def in_(self, column: str, values: list[Any]) -> "SyncQueryBuilder":
489
+ def in_(self, column: str, values: List[Any]) -> "SyncQueryBuilder":
490
490
  qb = self._clone()
491
491
  qb._conditions.append((column, "IN", tuple(values)))
492
492
  return qb
493
493
 
494
- def not_in(self, column: str, values: list[Any]) -> "SyncQueryBuilder":
494
+ def not_in(self, column: str, values: List[Any]) -> "SyncQueryBuilder":
495
495
  qb = self._clone()
496
496
  qb._conditions.append((column, "NOT IN", tuple(values)))
497
497
  return qb
@@ -508,16 +508,16 @@ class SyncQueryBuilder:
508
508
  return qb
509
509
 
510
510
  # JOIN
511
- def join(self, other_table: str, on: dict[str, str]) -> "SyncQueryBuilder":
511
+ def join(self, other_table: str, on: Dict[str, str]) -> "SyncQueryBuilder":
512
512
  qb = self._clone()
513
513
  qb._joins.append((other_table, on))
514
514
  return qb
515
515
 
516
516
  # Compile to SQL
517
- def _compile(self) -> tuple[str, list[Any]]:
517
+ def _compile(self) -> Tuple[str, List[Any]]:
518
518
  cols = ", ".join(self._select_cols)
519
519
  sql = [f"SELECT {cols} FROM {self._table}"]
520
- params: list[Any] = []
520
+ params: List[Any] = []
521
521
 
522
522
  # Joins
523
523
  for tbl, onmap in self._joins:
@@ -558,11 +558,11 @@ class SyncQueryBuilder:
558
558
  return row_dict.get("__cnt__", 0)
559
559
  return 0
560
560
 
561
- def first(self) -> dict[str, Any] | None:
561
+ def first(self) -> Optional[Dict[str, Any]]:
562
562
  rows = self.limit(1).all()
563
563
  return rows[0] if rows else None
564
564
 
565
- def all(self) -> list[dict[str, Any]]:
565
+ def all(self) -> List[Dict[str, Any]]:
566
566
  sql, params = self._compile()
567
567
  response = self._resource.query(sql, params)
568
568
  if not response.rows:
@@ -651,9 +651,7 @@ class SQLiteResource(Resource):
651
651
  )
652
652
  return DescribeResponse(**response.json())
653
653
 
654
- def query(
655
- self, query: str, args: Optional[List[Any]] = None
656
- ) -> QueryResponse:
654
+ def query(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
657
655
  return self._query(query, args, read_only=True)
658
656
 
659
657
  def exec(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
@@ -674,7 +672,7 @@ class SQLiteResource(Resource):
674
672
  """Create a query builder for the specified table."""
675
673
  return SyncQueryBuilder(self, table_name)
676
674
 
677
- def snapshot(self, name: str | None = None) -> SyncDatabaseSnapshot:
675
+ def snapshot(self, name: Optional[str] = None) -> SyncDatabaseSnapshot:
678
676
  """Create a snapshot of the current database state."""
679
677
  snapshot = SyncDatabaseSnapshot(self, name)
680
678
  snapshot._ensure_fetched()
@@ -683,7 +681,7 @@ class SQLiteResource(Resource):
683
681
  def diff(
684
682
  self,
685
683
  other: "SQLiteResource",
686
- ignore_config: IgnoreConfig | None = None,
684
+ ignore_config: Optional[IgnoreConfig] = None,
687
685
  ) -> SyncSnapshotDiff:
688
686
  """Compare this database with another AsyncSQLiteResource.
689
687
 
@@ -695,12 +693,8 @@ class SQLiteResource(Resource):
695
693
  AsyncSnapshotDiff: Object containing the differences between the two databases
696
694
  """
697
695
  # Create snapshots of both databases
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
- )
696
+ before_snapshot = self.snapshot(name=f"before_{datetime.utcnow().isoformat()}")
697
+ after_snapshot = other.snapshot(name=f"after_{datetime.utcnow().isoformat()}")
704
698
 
705
699
  # Return the diff between the snapshots
706
700
  return before_snapshot.diff(after_snapshot, ignore_config)
fleet/tasks.py CHANGED
@@ -2,11 +2,9 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import re
6
5
  import asyncio
7
6
  from datetime import datetime
8
7
  from typing import Any, Dict, Optional, List
9
- from uuid import UUID
10
8
 
11
9
  from pydantic import BaseModel, Field, validator
12
10
 
@@ -27,7 +25,9 @@ class Task(BaseModel):
27
25
  version: Optional[str] = Field(None, description="Task version")
28
26
  verifier_func: Optional[str] = Field(None, description="Verifier function code")
29
27
  verifier: Optional[Any] = Field(
30
- None, description="Verifier function with decorator (async or sync)"
28
+ None,
29
+ description="Verifier function with decorator (async or sync)",
30
+ exclude=True,
31
31
  )
32
32
  verifier_id: Optional[str] = Field(None, description="Verifier identifier")
33
33
  verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
@@ -47,7 +47,7 @@ class Task(BaseModel):
47
47
  @property
48
48
  def env_key(self) -> str:
49
49
  """Get the environment key combining env_id and version."""
50
- if self.version and self.version != "None":
50
+ if self.version and self.version != "None" and ":" not in self.env_id:
51
51
  return f"{self.env_id}:{self.version}"
52
52
  return self.env_id
53
53
 
@@ -66,21 +66,21 @@ class Task(BaseModel):
66
66
  For sync environments, calls the sync verifier directly.
67
67
  For async verifiers, automatically runs them with asyncio.run().
68
68
  """
69
+ # If verifier doesn't exist but verifier_func does, rebuild it
70
+ if not self.verifier and self.verifier_func:
71
+ self._rebuild_verifier()
72
+
69
73
  if self.verifier:
70
74
  import inspect
71
75
 
72
76
  # Check if verifier has remote method (for decorated verifiers)
73
- if hasattr(self.verifier, "remote"):
74
- result = self.verifier.remote(env, *args, **kwargs)
75
- else:
76
- # For verifiers created from string, call directly
77
- result = self.verifier(env, *args, **kwargs)
77
+ result = self.verifier.remote(env, *args, **kwargs)
78
78
 
79
79
  # If the result is a coroutine, we need to run it
80
80
  if inspect.iscoroutine(result):
81
81
  # Check if we're already in an event loop
82
82
  try:
83
- loop = asyncio.get_running_loop()
83
+ asyncio.get_running_loop()
84
84
  # We're in an async context, can't use asyncio.run()
85
85
  raise RuntimeError(
86
86
  "Cannot run async verifier in sync mode while event loop is running. "
@@ -100,6 +100,10 @@ class Task(BaseModel):
100
100
  For async environments, awaits the async verifier.
101
101
  Works with both sync and async verifiers in async contexts.
102
102
  """
103
+ # If verifier doesn't exist but verifier_func does, rebuild it
104
+ if not self.verifier and self.verifier_func:
105
+ self._rebuild_verifier()
106
+
103
107
  if self.verifier:
104
108
  result = self.verifier.remote(*args, **kwargs)
105
109
  # If it's a coroutine, await it
@@ -112,6 +116,19 @@ class Task(BaseModel):
112
116
  else:
113
117
  raise ValueError("No verifier function found for this task")
114
118
 
119
+ def _rebuild_verifier(self):
120
+ """Rebuild the verifier from verifier_func string if it exists."""
121
+ if self.verifier_func:
122
+ # Use the same logic as in verifier_from_string
123
+ verifier_id = self.verifier_id or self.key
124
+ verifier = verifier_from_string(
125
+ verifier_func=self.verifier_func,
126
+ verifier_id=verifier_id,
127
+ verifier_key=self.key,
128
+ sha256=self.verifier_sha or "",
129
+ )
130
+ self.verifier = verifier
131
+
115
132
  def make_env(self, region: Optional[str] = None):
116
133
  """Create an environment instance for this task's environment.
117
134
 
@@ -141,7 +158,7 @@ def verifier_from_string(
141
158
  """
142
159
  try:
143
160
  import inspect
144
- from .verifiers import verifier, SyncVerifierFunction
161
+ from .verifiers import SyncVerifierFunction
145
162
  from .verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
146
163
  from .verifiers.db import IgnoreConfig
147
164
 
@@ -172,36 +189,13 @@ def verifier_from_string(
172
189
  if func_obj is None:
173
190
  raise ValueError("No function found in verifier code")
174
191
 
175
- # Create a wrapper function that provides the necessary globals
176
- def wrapped_verifier(env, *args, **kwargs):
177
- # Set up globals for the function execution
178
- func_globals = (
179
- func_obj.__globals__.copy() if hasattr(func_obj, "__globals__") else {}
180
- )
181
- func_globals.update(
182
- {
183
- "TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
184
- "TASK_FAILED_SCORE": TASK_FAILED_SCORE,
185
- "IgnoreConfig": IgnoreConfig,
186
- }
187
- )
188
-
189
- # Create a new function with the updated globals
190
- import types
191
-
192
- new_func = types.FunctionType(
193
- func_obj.__code__,
194
- func_globals,
195
- func_obj.__name__,
196
- func_obj.__defaults__,
197
- func_obj.__closure__,
198
- )
199
-
200
- return new_func(env, *args, **kwargs)
201
-
202
- # Create an AsyncVerifierFunction instance with the wrapped function
192
+ # Create an SyncVerifierFunction instance with raw code
203
193
  verifier_instance = SyncVerifierFunction(
204
- wrapped_verifier, verifier_key, verifier_id
194
+ func=func_obj,
195
+ key=verifier_key,
196
+ verifier_id=verifier_id,
197
+ sha256=sha256,
198
+ raw_code=verifier_func,
205
199
  )
206
200
 
207
201
  # Store additional metadata
@@ -214,6 +208,18 @@ def verifier_from_string(
214
208
  raise ValueError(f"Failed to create verifier from string: {e}")
215
209
 
216
210
 
211
+ def load_tasks_from_file(filename: str) -> List[Task]:
212
+ """Load tasks from a JSON file.
213
+
214
+ Example:
215
+ tasks = fleet.load_tasks_from_file("my_tasks.json")
216
+ """
217
+ from .global_client import get_client
218
+
219
+ client = get_client()
220
+ return client.load_tasks_from_file(filename)
221
+
222
+
217
223
  def load_tasks(
218
224
  env_key: Optional[str] = None,
219
225
  keys: Optional[List[str]] = None,
@@ -260,7 +266,6 @@ def update_task(
260
266
  response = fleet.update_task("my-task", verifier_code="def verify(env): return True")
261
267
  """
262
268
  from .global_client import get_client
263
- from .models import TaskResponse
264
269
 
265
270
  client = get_client()
266
271
  return client.update_task(
@@ -2,7 +2,7 @@
2
2
 
3
3
  from .db import DatabaseSnapshot, IgnoreConfig, SnapshotDiff
4
4
  from .code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
5
- from .decorator import (
5
+ from .verifier import (
6
6
  verifier,
7
7
  SyncVerifierFunction,
8
8
  )