fleet-python 0.2.28__py3-none-any.whl → 0.2.32__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 +30 -20
- examples/dsl_example.py +12 -7
- examples/example.py +4 -4
- examples/example_account.py +8 -0
- examples/example_action_log.py +2 -2
- examples/example_client.py +2 -2
- examples/example_mcp_anthropic.py +8 -5
- examples/example_mcp_openai.py +2 -2
- examples/example_sync.py +4 -4
- examples/example_task.py +16 -6
- examples/example_tasks.py +3 -6
- examples/example_verifier.py +16 -3
- examples/gemini_example.py +6 -6
- examples/json_tasks_example.py +2 -2
- examples/nova_act_example.py +2 -2
- examples/openai_example.py +3 -3
- examples/openai_simple_example.py +3 -3
- examples/query_builder_example.py +11 -7
- fleet/__init__.py +60 -5
- fleet/_async/__init__.py +258 -1
- fleet/_async/base.py +2 -1
- fleet/_async/client.py +194 -127
- fleet/_async/env/client.py +5 -1
- fleet/_async/global_client.py +43 -0
- fleet/_async/instance/client.py +1 -1
- fleet/_async/models.py +172 -171
- fleet/_async/resources/base.py +1 -1
- fleet/_async/resources/mcp.py +55 -0
- fleet/_async/resources/sqlite.py +141 -130
- fleet/_async/tasks.py +71 -16
- fleet/_async/verifiers/__init__.py +2 -2
- fleet/_async/verifiers/bundler.py +18 -14
- fleet/_async/verifiers/verifier.py +77 -71
- fleet/base.py +2 -1
- fleet/client.py +176 -136
- fleet/config.py +3 -2
- fleet/env/__init__.py +10 -1
- fleet/env/client.py +5 -1
- fleet/global_client.py +43 -0
- fleet/instance/__init__.py +1 -1
- fleet/instance/client.py +2 -4
- fleet/models.py +172 -171
- fleet/resources/base.py +1 -1
- fleet/resources/mcp.py +27 -33
- fleet/resources/sqlite.py +136 -131
- fleet/tasks.py +197 -16
- fleet/types.py +1 -1
- fleet/verifiers/__init__.py +2 -2
- fleet/verifiers/bundler.py +18 -14
- fleet/verifiers/code.py +1 -1
- fleet/verifiers/decorator.py +25 -34
- fleet/verifiers/parse.py +98 -68
- fleet/verifiers/verifier.py +77 -78
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/METADATA +9 -9
- fleet_python-0.2.32.dist-info/RECORD +74 -0
- scripts/fix_sync_imports.py +87 -59
- scripts/unasync.py +10 -9
- fleet_python-0.2.28.dist-info/RECORD +0 -70
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.28.dist-info → fleet_python-0.2.32.dist-info}/top_level.txt +0 -0
fleet/tasks.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
import re
|
|
6
7
|
from datetime import datetime
|
|
7
|
-
from typing import Any, Dict, Optional
|
|
8
|
-
from uuid import UUID
|
|
8
|
+
from typing import Any, Dict, Optional, List
|
|
9
9
|
|
|
10
10
|
from pydantic import BaseModel, Field, validator
|
|
11
11
|
|
|
@@ -13,31 +13,168 @@ 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
|
+
|
|
16
143
|
class Task(BaseModel):
|
|
17
144
|
"""A task model representing a single task in the Fleet system."""
|
|
18
|
-
|
|
145
|
+
|
|
19
146
|
key: str = Field(..., description="Unique task key identifier")
|
|
20
147
|
prompt: str = Field(..., description="Task prompt or instruction")
|
|
21
148
|
env_id: str = Field(..., description="Environment identifier")
|
|
22
|
-
env_variables: Optional[Dict[str, Any]] = Field(
|
|
149
|
+
env_variables: Optional[Dict[str, Any]] = Field(
|
|
150
|
+
default_factory=dict, description="Environment variables"
|
|
151
|
+
)
|
|
23
152
|
created_at: Optional[datetime] = Field(None, description="Task creation timestamp")
|
|
24
153
|
version: Optional[str] = Field(None, description="Task version")
|
|
25
154
|
verifier_func: Optional[str] = Field(None, description="Verifier function code")
|
|
26
|
-
verifier: Optional[Any] = Field(
|
|
27
|
-
|
|
155
|
+
verifier: Optional[Any] = Field(
|
|
156
|
+
None, description="Verifier function with decorator (async or sync)"
|
|
157
|
+
)
|
|
158
|
+
verifier_id: Optional[str] = Field(None, description="Verifier identifier")
|
|
159
|
+
verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
|
|
160
|
+
metadata: Optional[Dict[str, Any]] = Field(
|
|
161
|
+
default_factory=dict, description="Additional task metadata"
|
|
162
|
+
)
|
|
28
163
|
|
|
29
|
-
@validator(
|
|
164
|
+
@validator("key")
|
|
30
165
|
def validate_key_format(cls, v):
|
|
31
166
|
"""Validate key follows kebab-case format."""
|
|
32
|
-
if not re.match(r
|
|
33
|
-
raise ValueError(
|
|
167
|
+
if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", v):
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"Invalid task key format: {v}. Must follow kebab-case format."
|
|
170
|
+
)
|
|
34
171
|
return v
|
|
35
172
|
|
|
36
|
-
@validator(
|
|
173
|
+
@validator("created_at", pre=True, always=True)
|
|
37
174
|
def set_created_at(cls, v):
|
|
38
175
|
"""Set created_at to current time if not provided."""
|
|
39
176
|
return v or datetime.now()
|
|
40
|
-
|
|
177
|
+
|
|
41
178
|
@property
|
|
42
179
|
def env_key(self) -> str:
|
|
43
180
|
"""Get the environment key combining env_id and version."""
|
|
@@ -47,26 +184,45 @@ class Task(BaseModel):
|
|
|
47
184
|
|
|
48
185
|
class Config:
|
|
49
186
|
"""Pydantic model configuration."""
|
|
187
|
+
|
|
50
188
|
json_encoders = {
|
|
51
189
|
datetime: lambda v: v.isoformat(),
|
|
52
190
|
}
|
|
53
191
|
# Allow arbitrary types for the verifier field
|
|
54
|
-
arbitrary_types_allowed = True
|
|
192
|
+
arbitrary_types_allowed = True
|
|
55
193
|
|
|
56
194
|
def verify(self, env, *args, **kwargs) -> float:
|
|
57
195
|
"""Verify the task using the verifier function (sync version).
|
|
58
|
-
|
|
196
|
+
|
|
59
197
|
For sync environments, calls the sync verifier directly.
|
|
60
198
|
For async verifiers, automatically runs them with asyncio.run().
|
|
61
199
|
"""
|
|
62
200
|
if self.verifier:
|
|
63
|
-
|
|
201
|
+
import inspect
|
|
202
|
+
|
|
203
|
+
result = self.verifier.remote(env, *args, **kwargs)
|
|
204
|
+
|
|
205
|
+
# If the result is a coroutine, we need to run it
|
|
206
|
+
if inspect.iscoroutine(result):
|
|
207
|
+
# Check if we're already in an event loop
|
|
208
|
+
try:
|
|
209
|
+
_ = asyncio.get_running_loop()
|
|
210
|
+
# We're in an async context, can't use asyncio.run()
|
|
211
|
+
raise RuntimeError(
|
|
212
|
+
"Cannot run async verifier in sync mode while event loop is running. "
|
|
213
|
+
"Use await task.verify_async() instead."
|
|
214
|
+
)
|
|
215
|
+
except RuntimeError:
|
|
216
|
+
# No event loop running, safe to use asyncio.run()
|
|
217
|
+
return asyncio.run(result)
|
|
218
|
+
else:
|
|
219
|
+
return result
|
|
64
220
|
else:
|
|
65
221
|
raise ValueError("No verifier function found for this task")
|
|
66
|
-
|
|
222
|
+
|
|
67
223
|
def verify_async(self, *args, **kwargs) -> float:
|
|
68
224
|
"""Verify the task using the verifier function (async version).
|
|
69
|
-
|
|
225
|
+
|
|
70
226
|
For async environments, awaits the async verifier.
|
|
71
227
|
Works with both sync and async verifiers in async contexts.
|
|
72
228
|
"""
|
|
@@ -74,9 +230,34 @@ class Task(BaseModel):
|
|
|
74
230
|
result = self.verifier.remote(*args, **kwargs)
|
|
75
231
|
# If it's a coroutine, await it
|
|
76
232
|
import inspect
|
|
233
|
+
|
|
77
234
|
if inspect.iscoroutine(result):
|
|
78
235
|
return result
|
|
79
236
|
else:
|
|
80
237
|
return result
|
|
81
238
|
else:
|
|
82
239
|
raise ValueError("No verifier function found for this task")
|
|
240
|
+
|
|
241
|
+
def make_env(self, region: Optional[str] = None):
|
|
242
|
+
"""Create an environment instance for this task's environment.
|
|
243
|
+
|
|
244
|
+
Uses the task's env_id (and version if present) to create the env.
|
|
245
|
+
"""
|
|
246
|
+
if not self.env_id:
|
|
247
|
+
raise ValueError("Task has no env_id defined")
|
|
248
|
+
# Deferred import to avoid circular dependencies
|
|
249
|
+
from .client import Fleet
|
|
250
|
+
|
|
251
|
+
return Fleet().make(env_key=self.env_key, region=region)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def load_tasks(env_key: Optional[str] = None) -> List[Task]:
|
|
255
|
+
"""Convenience function to load tasks without initializing a client.
|
|
256
|
+
|
|
257
|
+
Creates an `AsyncFleet` client under the hood and returns the tasks list.
|
|
258
|
+
"""
|
|
259
|
+
# Use the global client by default so users can pre-configure it once
|
|
260
|
+
from .global_client import get_client
|
|
261
|
+
|
|
262
|
+
client = get_client()
|
|
263
|
+
return client.load_tasks(env_key=env_key)
|
fleet/types.py
CHANGED
|
@@ -15,4 +15,4 @@ if TYPE_CHECKING:
|
|
|
15
15
|
|
|
16
16
|
# Union type to support both async and sync verifiers
|
|
17
17
|
# This definition works for both the async and sync versions of the codebase
|
|
18
|
-
VerifierFunction = Union["SyncVerifierFunction", "AsyncVerifierFunction"]
|
|
18
|
+
VerifierFunction = Union["SyncVerifierFunction", "AsyncVerifierFunction"]
|
fleet/verifiers/__init__.py
CHANGED
fleet/verifiers/bundler.py
CHANGED
|
@@ -544,7 +544,9 @@ class FunctionBundler:
|
|
|
544
544
|
# Ensure fleet-python is always included
|
|
545
545
|
if not requirements:
|
|
546
546
|
requirements = ["fleet-python"]
|
|
547
|
-
elif "fleet-python" not in [
|
|
547
|
+
elif "fleet-python" not in [
|
|
548
|
+
r.split("==")[0].split(">=")[0] for r in requirements
|
|
549
|
+
]:
|
|
548
550
|
requirements.append("fleet-python")
|
|
549
551
|
requirements_file.write_text("\n".join(sorted(set(requirements))))
|
|
550
552
|
|
|
@@ -663,37 +665,39 @@ class FunctionBundler:
|
|
|
663
665
|
logger.warning(f"Failed to extract function {function_name}: {e}")
|
|
664
666
|
|
|
665
667
|
return None
|
|
666
|
-
|
|
668
|
+
|
|
667
669
|
def _get_function_source_without_decorator(self, func: Callable) -> str:
|
|
668
670
|
"""Get function source code without the @verifier decorator."""
|
|
669
671
|
source = inspect.getsource(func)
|
|
670
|
-
lines = source.split(
|
|
671
|
-
|
|
672
|
+
lines = source.split("\n")
|
|
673
|
+
|
|
672
674
|
# Find where the function definition starts
|
|
673
675
|
func_start = -1
|
|
674
676
|
for i, line in enumerate(lines):
|
|
675
|
-
if line.strip().startswith(
|
|
677
|
+
if line.strip().startswith("def "):
|
|
676
678
|
func_start = i
|
|
677
679
|
break
|
|
678
|
-
|
|
680
|
+
|
|
679
681
|
if func_start == -1:
|
|
680
682
|
# Couldn't find function definition, return original
|
|
681
683
|
return source
|
|
682
|
-
|
|
684
|
+
|
|
683
685
|
# Return only from the function definition onward
|
|
684
686
|
func_lines = lines[func_start:]
|
|
685
|
-
|
|
687
|
+
|
|
686
688
|
# Remove common indentation
|
|
687
689
|
if func_lines:
|
|
688
690
|
# Find minimum indentation (excluding empty lines)
|
|
689
|
-
min_indent = float(
|
|
691
|
+
min_indent = float("inf")
|
|
690
692
|
for line in func_lines:
|
|
691
693
|
if line.strip():
|
|
692
694
|
indent = len(line) - len(line.lstrip())
|
|
693
695
|
min_indent = min(min_indent, indent)
|
|
694
|
-
|
|
696
|
+
|
|
695
697
|
# Remove the common indentation
|
|
696
|
-
if min_indent < float(
|
|
697
|
-
func_lines = [
|
|
698
|
-
|
|
699
|
-
|
|
698
|
+
if min_indent < float("inf"):
|
|
699
|
+
func_lines = [
|
|
700
|
+
line[min_indent:] if line.strip() else line for line in func_lines
|
|
701
|
+
]
|
|
702
|
+
|
|
703
|
+
return "\n".join(func_lines)
|
fleet/verifiers/code.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
TASK_SUCCESSFUL_SCORE = 1
|
|
2
|
-
TASK_FAILED_SCORE = 0
|
|
2
|
+
TASK_FAILED_SCORE = 0
|
fleet/verifiers/decorator.py
CHANGED
|
@@ -13,34 +13,26 @@ import logging
|
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
F = TypeVar(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class SyncVerifierFunction:
|
|
23
20
|
"""Wrapper for a verified function that supports local execution with env-first pattern."""
|
|
24
|
-
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
func: F,
|
|
28
|
-
key: str,
|
|
29
|
-
verifier_id: str
|
|
30
|
-
):
|
|
21
|
+
|
|
22
|
+
def __init__(self, func: F, key: str, verifier_id: str):
|
|
31
23
|
self.func = func
|
|
32
24
|
self.key = key
|
|
33
25
|
self.name = key # Keep name for backward compatibility
|
|
34
26
|
self.verifier_id = verifier_id
|
|
35
|
-
|
|
27
|
+
|
|
36
28
|
# Copy function metadata
|
|
37
29
|
functools.update_wrapper(self, func)
|
|
38
|
-
|
|
30
|
+
|
|
39
31
|
def __call__(self, env, *args, **kwargs) -> float:
|
|
40
32
|
"""Local execution of the verifier function with env as first parameter."""
|
|
41
33
|
try:
|
|
42
34
|
result = self.func(env, *args, **kwargs)
|
|
43
|
-
|
|
35
|
+
|
|
44
36
|
# Handle different return types
|
|
45
37
|
if isinstance(result, (int, float)):
|
|
46
38
|
# Direct score return
|
|
@@ -49,31 +41,33 @@ class SyncVerifierFunction:
|
|
|
49
41
|
return float(result["score"])
|
|
50
42
|
else:
|
|
51
43
|
# Try to extract score from object attributes
|
|
52
|
-
if hasattr(result,
|
|
44
|
+
if hasattr(result, "score"):
|
|
53
45
|
return float(result.score)
|
|
54
46
|
else:
|
|
55
|
-
raise ValueError(
|
|
56
|
-
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Verifier function must return a score (number). Got {type(result)}"
|
|
49
|
+
)
|
|
50
|
+
|
|
57
51
|
except Exception as e:
|
|
58
52
|
logger.error(f"Error in verifier {self.key}: {e}")
|
|
59
53
|
# Return error score 0
|
|
60
54
|
return 0.0
|
|
61
55
|
|
|
56
|
+
|
|
62
57
|
def verifier(
|
|
63
|
-
key: Optional[str] = None,
|
|
64
|
-
verifier_id: Optional[str] = None
|
|
58
|
+
key: Optional[str] = None, verifier_id: Optional[str] = None
|
|
65
59
|
) -> Callable[[F], SyncVerifierFunction]:
|
|
66
60
|
"""
|
|
67
61
|
Decorator to create a verifier function with env-first pattern.
|
|
68
|
-
|
|
62
|
+
|
|
69
63
|
The decorated function must take 'env' as its first parameter, making it explicit
|
|
70
64
|
that verifiers operate within an environment context. This makes verifiers reusable
|
|
71
65
|
across different environments.
|
|
72
|
-
|
|
66
|
+
|
|
73
67
|
Args:
|
|
74
68
|
key: Optional key for the verifier. Defaults to function name.
|
|
75
69
|
verifier_id: Optional unique ID for the verifier. Defaults to generated UUID.
|
|
76
|
-
|
|
70
|
+
|
|
77
71
|
Example:
|
|
78
72
|
@verifier(key="test_database_state")
|
|
79
73
|
def check_user_count(env, expected_count: int) -> float:
|
|
@@ -81,23 +75,20 @@ def verifier(
|
|
|
81
75
|
result = db.query("SELECT COUNT(*) FROM users")
|
|
82
76
|
actual_count = result.rows[0][0]
|
|
83
77
|
return 1.0 if actual_count >= expected_count else 0.0
|
|
84
|
-
|
|
78
|
+
|
|
85
79
|
# Usage with different environments
|
|
86
|
-
env1 =
|
|
87
|
-
env2 =
|
|
88
|
-
|
|
80
|
+
env1 = fleet.env.make("fira")
|
|
81
|
+
env2 = fleet.env.make("another_env")
|
|
82
|
+
|
|
89
83
|
# Local execution
|
|
90
84
|
result = await check_user_count(env1, 5)
|
|
91
85
|
result = await check_user_count(env2, 5) # Same verifier, different env
|
|
92
86
|
"""
|
|
87
|
+
|
|
93
88
|
def decorator(func: F) -> SyncVerifierFunction:
|
|
94
89
|
verifier_key = key or func.__name__
|
|
95
90
|
verifier_uuid = verifier_id or str(uuid.uuid4())
|
|
96
|
-
|
|
97
|
-
return SyncVerifierFunction(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
verifier_uuid
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return decorator
|
|
91
|
+
|
|
92
|
+
return SyncVerifierFunction(func, verifier_key, verifier_uuid)
|
|
93
|
+
|
|
94
|
+
return decorator
|