fleet-python 0.2.29__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.

Files changed (61) hide show
  1. examples/diff_example.py +30 -20
  2. examples/dsl_example.py +12 -7
  3. examples/example.py +4 -4
  4. examples/example_account.py +8 -0
  5. examples/example_action_log.py +2 -2
  6. examples/example_client.py +2 -2
  7. examples/example_mcp_anthropic.py +8 -5
  8. examples/example_mcp_openai.py +2 -2
  9. examples/example_sync.py +4 -4
  10. examples/example_task.py +16 -6
  11. examples/example_tasks.py +3 -6
  12. examples/example_verifier.py +16 -3
  13. examples/gemini_example.py +6 -6
  14. examples/json_tasks_example.py +2 -2
  15. examples/nova_act_example.py +2 -2
  16. examples/openai_example.py +3 -3
  17. examples/openai_simple_example.py +3 -3
  18. examples/query_builder_example.py +11 -7
  19. fleet/__init__.py +60 -5
  20. fleet/_async/__init__.py +258 -1
  21. fleet/_async/base.py +2 -1
  22. fleet/_async/client.py +164 -144
  23. fleet/_async/env/client.py +2 -0
  24. fleet/_async/global_client.py +43 -0
  25. fleet/_async/instance/client.py +1 -1
  26. fleet/_async/models.py +172 -171
  27. fleet/_async/resources/base.py +1 -1
  28. fleet/_async/resources/mcp.py +55 -0
  29. fleet/_async/resources/sqlite.py +141 -130
  30. fleet/_async/tasks.py +69 -16
  31. fleet/_async/verifiers/__init__.py +2 -2
  32. fleet/_async/verifiers/bundler.py +18 -14
  33. fleet/_async/verifiers/verifier.py +77 -71
  34. fleet/base.py +2 -1
  35. fleet/client.py +145 -158
  36. fleet/config.py +3 -2
  37. fleet/env/__init__.py +9 -1
  38. fleet/env/client.py +3 -0
  39. fleet/global_client.py +43 -0
  40. fleet/instance/__init__.py +1 -1
  41. fleet/instance/client.py +2 -4
  42. fleet/models.py +172 -171
  43. fleet/resources/base.py +1 -1
  44. fleet/resources/mcp.py +27 -33
  45. fleet/resources/sqlite.py +136 -131
  46. fleet/tasks.py +195 -16
  47. fleet/types.py +1 -1
  48. fleet/verifiers/__init__.py +2 -2
  49. fleet/verifiers/bundler.py +18 -14
  50. fleet/verifiers/code.py +1 -1
  51. fleet/verifiers/decorator.py +25 -34
  52. fleet/verifiers/parse.py +98 -68
  53. fleet/verifiers/verifier.py +77 -78
  54. {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/METADATA +9 -9
  55. fleet_python-0.2.32.dist-info/RECORD +74 -0
  56. scripts/fix_sync_imports.py +87 -59
  57. scripts/unasync.py +10 -9
  58. fleet_python-0.2.29.dist-info/RECORD +0 -70
  59. {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/WHEEL +0 -0
  60. {fleet_python-0.2.29.dist-info → fleet_python-0.2.32.dist-info}/licenses/LICENSE +0 -0
  61. {fleet_python-0.2.29.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,33 +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(default_factory=dict, description="Environment variables")
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(None, description="Verifier function with decorator (async or sync)")
155
+ verifier: Optional[Any] = Field(
156
+ None, description="Verifier function with decorator (async or sync)"
157
+ )
27
158
  verifier_id: Optional[str] = Field(None, description="Verifier identifier")
28
159
  verifier_sha: Optional[str] = Field(None, description="Verifier SHA256 hash")
29
- metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional task metadata")
160
+ metadata: Optional[Dict[str, Any]] = Field(
161
+ default_factory=dict, description="Additional task metadata"
162
+ )
30
163
 
31
- @validator('key')
164
+ @validator("key")
32
165
  def validate_key_format(cls, v):
33
166
  """Validate key follows kebab-case format."""
34
- if not re.match(r'^[a-z0-9]+(-[a-z0-9]+)*$', v):
35
- raise ValueError(f'Invalid task key format: {v}. Must follow kebab-case format.')
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
+ )
36
171
  return v
37
172
 
38
- @validator('created_at', pre=True, always=True)
173
+ @validator("created_at", pre=True, always=True)
39
174
  def set_created_at(cls, v):
40
175
  """Set created_at to current time if not provided."""
41
176
  return v or datetime.now()
42
-
177
+
43
178
  @property
44
179
  def env_key(self) -> str:
45
180
  """Get the environment key combining env_id and version."""
@@ -49,26 +184,45 @@ class Task(BaseModel):
49
184
 
50
185
  class Config:
51
186
  """Pydantic model configuration."""
187
+
52
188
  json_encoders = {
53
189
  datetime: lambda v: v.isoformat(),
54
190
  }
55
191
  # Allow arbitrary types for the verifier field
56
- arbitrary_types_allowed = True
192
+ arbitrary_types_allowed = True
57
193
 
58
194
  def verify(self, env, *args, **kwargs) -> float:
59
195
  """Verify the task using the verifier function (sync version).
60
-
196
+
61
197
  For sync environments, calls the sync verifier directly.
62
198
  For async verifiers, automatically runs them with asyncio.run().
63
199
  """
64
200
  if self.verifier:
65
- return self.verifier.remote(env, *args, **kwargs)
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
66
220
  else:
67
221
  raise ValueError("No verifier function found for this task")
68
-
222
+
69
223
  def verify_async(self, *args, **kwargs) -> float:
70
224
  """Verify the task using the verifier function (async version).
71
-
225
+
72
226
  For async environments, awaits the async verifier.
73
227
  Works with both sync and async verifiers in async contexts.
74
228
  """
@@ -76,9 +230,34 @@ class Task(BaseModel):
76
230
  result = self.verifier.remote(*args, **kwargs)
77
231
  # If it's a coroutine, await it
78
232
  import inspect
233
+
79
234
  if inspect.iscoroutine(result):
80
235
  return result
81
236
  else:
82
237
  return result
83
238
  else:
84
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"]
@@ -9,10 +9,10 @@ from .decorator import (
9
9
 
10
10
  __all__ = [
11
11
  "DatabaseSnapshot",
12
- "IgnoreConfig",
12
+ "IgnoreConfig",
13
13
  "SnapshotDiff",
14
14
  "TASK_SUCCESSFUL_SCORE",
15
15
  "TASK_FAILED_SCORE",
16
16
  "verifier",
17
17
  "SyncVerifierFunction",
18
- ]
18
+ ]
@@ -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 [r.split("==")[0].split(">=")[0] for r in requirements]:
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('\n')
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('def '):
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('inf')
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('inf'):
697
- func_lines = [line[min_indent:] if line.strip() else line for line in func_lines]
698
-
699
- return '\n'.join(func_lines)
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
@@ -13,34 +13,26 @@ import logging
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
 
16
- F = TypeVar('F', bound=Callable[..., Any])
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, 'score'):
44
+ if hasattr(result, "score"):
53
45
  return float(result.score)
54
46
  else:
55
- raise ValueError(f"Verifier function must return a score (number). Got {type(result)}")
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 = flt.env.make("fira")
87
- env2 = flt.env.make("another_env")
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
- func,
99
- verifier_key,
100
- verifier_uuid
101
- )
102
-
103
- return decorator
91
+
92
+ return SyncVerifierFunction(func, verifier_key, verifier_uuid)
93
+
94
+ return decorator