fleet-python 0.2.116__tar.gz → 0.2.118__tar.gz

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.
Files changed (126) hide show
  1. {fleet_python-0.2.116/fleet_python.egg-info → fleet_python-0.2.118}/PKG-INFO +1 -1
  2. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/__init__.py +7 -1
  3. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/__init__.py +1 -1
  4. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/base.py +1 -1
  5. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/client.py +68 -1
  6. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/base.py +1 -1
  7. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/client.py +62 -1
  8. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/__init__.py +4 -0
  9. fleet_python-0.2.118/fleet/verifiers/local_executor.py +371 -0
  10. {fleet_python-0.2.116 → fleet_python-0.2.118/fleet_python.egg-info}/PKG-INFO +1 -1
  11. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet_python.egg-info/SOURCES.txt +1 -0
  12. {fleet_python-0.2.116 → fleet_python-0.2.118}/pyproject.toml +1 -1
  13. {fleet_python-0.2.116 → fleet_python-0.2.118}/LICENSE +0 -0
  14. {fleet_python-0.2.116 → fleet_python-0.2.118}/README.md +0 -0
  15. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/diff_example.py +0 -0
  16. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/dsl_example.py +0 -0
  17. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example.py +0 -0
  18. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/exampleResume.py +0 -0
  19. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_account.py +0 -0
  20. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_action_log.py +0 -0
  21. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_client.py +0 -0
  22. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_mcp_anthropic.py +0 -0
  23. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_mcp_openai.py +0 -0
  24. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_sync.py +0 -0
  25. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_task.py +0 -0
  26. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_tasks.py +0 -0
  27. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/example_verifier.py +0 -0
  28. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/export_tasks.py +0 -0
  29. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/export_tasks_filtered.py +0 -0
  30. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/fetch_tasks.py +0 -0
  31. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/gemini_example.py +0 -0
  32. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/import_tasks.py +0 -0
  33. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/iterate_verifiers.py +0 -0
  34. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/json_tasks_example.py +0 -0
  35. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/nova_act_example.py +0 -0
  36. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/openai_example.py +0 -0
  37. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/openai_simple_example.py +0 -0
  38. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/query_builder_example.py +0 -0
  39. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/quickstart.py +0 -0
  40. {fleet_python-0.2.116 → fleet_python-0.2.118}/examples/test_cdp_logging.py +0 -0
  41. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/env/__init__.py +0 -0
  42. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/env/client.py +0 -0
  43. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/exceptions.py +0 -0
  44. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/global_client.py +0 -0
  45. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/instance/__init__.py +0 -0
  46. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/instance/base.py +0 -0
  47. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/instance/client.py +0 -0
  48. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/judge.py +0 -0
  49. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/models.py +0 -0
  50. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/__init__.py +0 -0
  51. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/api.py +0 -0
  52. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/base.py +0 -0
  53. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/browser.py +0 -0
  54. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/filesystem.py +0 -0
  55. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/mcp.py +0 -0
  56. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/resources/sqlite.py +0 -0
  57. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/tasks.py +0 -0
  58. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/verifiers/__init__.py +0 -0
  59. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/verifiers/bundler.py +0 -0
  60. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/_async/verifiers/verifier.py +0 -0
  61. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/__init__.py +0 -0
  62. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/Dockerfile +0 -0
  63. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/__init__.py +0 -0
  64. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/agent.py +0 -0
  65. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/mcp/main.py +0 -0
  66. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/mcp_server/__init__.py +0 -0
  67. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/mcp_server/main.py +0 -0
  68. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/mcp_server/tools.py +0 -0
  69. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/requirements.txt +0 -0
  70. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/gemini_cua/start.sh +0 -0
  71. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/orchestrator.py +0 -0
  72. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/types.py +0 -0
  73. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/agent/utils.py +0 -0
  74. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/cli.py +0 -0
  75. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/config.py +0 -0
  76. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/env/__init__.py +0 -0
  77. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/env/client.py +0 -0
  78. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/eval/__init__.py +0 -0
  79. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/eval/uploader.py +0 -0
  80. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/exceptions.py +0 -0
  81. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/global_client.py +0 -0
  82. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/instance/__init__.py +0 -0
  83. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/instance/base.py +0 -0
  84. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/instance/client.py +0 -0
  85. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/instance/models.py +0 -0
  86. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/judge.py +0 -0
  87. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/models.py +0 -0
  88. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/proxy/__init__.py +0 -0
  89. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/proxy/proxy.py +0 -0
  90. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/proxy/whitelist.py +0 -0
  91. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/__init__.py +0 -0
  92. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/api.py +0 -0
  93. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/base.py +0 -0
  94. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/browser.py +0 -0
  95. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/filesystem.py +0 -0
  96. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/mcp.py +0 -0
  97. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/resources/sqlite.py +0 -0
  98. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/tasks.py +0 -0
  99. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/types.py +0 -0
  100. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/utils/__init__.py +0 -0
  101. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/utils/http_logging.py +0 -0
  102. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/utils/logging.py +0 -0
  103. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/utils/playwright.py +0 -0
  104. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/bundler.py +0 -0
  105. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/code.py +0 -0
  106. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/db.py +0 -0
  107. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/decorator.py +0 -0
  108. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/parse.py +0 -0
  109. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/sql_differ.py +0 -0
  110. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet/verifiers/verifier.py +0 -0
  111. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet_python.egg-info/dependency_links.txt +0 -0
  112. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet_python.egg-info/entry_points.txt +0 -0
  113. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet_python.egg-info/requires.txt +0 -0
  114. {fleet_python-0.2.116 → fleet_python-0.2.118}/fleet_python.egg-info/top_level.txt +0 -0
  115. {fleet_python-0.2.116 → fleet_python-0.2.118}/scripts/fix_sync_imports.py +0 -0
  116. {fleet_python-0.2.116 → fleet_python-0.2.118}/scripts/unasync.py +0 -0
  117. {fleet_python-0.2.116 → fleet_python-0.2.118}/setup.cfg +0 -0
  118. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/__init__.py +0 -0
  119. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_app_method.py +0 -0
  120. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_expect_exactly.py +0 -0
  121. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_expect_only.py +0 -0
  122. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_instance_dispatch.py +0 -0
  123. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_judge_criteria_markers.py +0 -0
  124. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_sqlite_resource_dual_mode.py +0 -0
  125. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_sqlite_shared_memory_behavior.py +0 -0
  126. {fleet_python-0.2.116 → fleet_python-0.2.118}/tests/test_verifier_from_string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.116
3
+ Version: 0.2.118
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -38,6 +38,9 @@ from .verifiers import (
38
38
  SnapshotDiff,
39
39
  TASK_FAILED_SCORE,
40
40
  TASK_SUCCESSFUL_SCORE,
41
+ execute_verifier_local,
42
+ LocalEnvironment,
43
+ diff_dbs,
41
44
  )
42
45
 
43
46
  # Import async verifiers (default verifier is async for modern usage)
@@ -76,7 +79,7 @@ from . import env
76
79
  from . import global_client as _global_client
77
80
  from ._async import global_client as _async_global_client
78
81
 
79
- __version__ = "0.2.116"
82
+ __version__ = "0.2.118"
80
83
 
81
84
  __all__ = [
82
85
  # Core classes
@@ -114,6 +117,9 @@ __all__ = [
114
117
  "SnapshotDiff",
115
118
  "TASK_FAILED_SCORE",
116
119
  "TASK_SUCCESSFUL_SCORE",
120
+ "execute_verifier_local",
121
+ "LocalEnvironment",
122
+ "diff_dbs",
117
123
  # Environment module
118
124
  "env",
119
125
  # Global client helpers
@@ -44,7 +44,7 @@ from ..types import VerifierFunction
44
44
  from .. import env
45
45
  from . import global_client as _async_global_client
46
46
 
47
- __version__ = "0.2.116"
47
+ __version__ = "0.2.118"
48
48
 
49
49
  __all__ = [
50
50
  # Core classes
@@ -26,7 +26,7 @@ from .exceptions import (
26
26
  try:
27
27
  from .. import __version__
28
28
  except ImportError:
29
- __version__ = "0.2.116"
29
+ __version__ = "0.2.118"
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
@@ -832,7 +832,74 @@ class AsyncFleet:
832
832
  At least one of run_id or profile_id must be provided.
833
833
  """
834
834
  return await _delete_instances_batch(self.client, run_id=run_id, profile_id=profile_id)
835
-
835
+
836
+ @staticmethod
837
+ async def execute_verifier_local(
838
+ verifier_func: str,
839
+ seed_db: str,
840
+ current_db: str,
841
+ final_answer: Optional[str] = None,
842
+ ) -> Dict[str, Any]:
843
+ """Execute a verifier function locally against SQLite database files.
844
+
845
+ No authentication or remote server required. The verifier code is executed
846
+ in an isolated namespace with the same helpers available in production
847
+ (``normalized_contains``, ``IgnoreConfig``, ``DatabaseSnapshot``, etc.).
848
+
849
+ Args:
850
+ verifier_func: Python source code containing the verifier function definition.
851
+ seed_db: Path to the seed (before) SQLite database file.
852
+ current_db: Path to the current (after) SQLite database file.
853
+ final_answer: Optional final answer string passed to the verifier.
854
+
855
+ Returns:
856
+ Dict with keys ``success``, ``result``, ``error``, and ``stdout``.
857
+
858
+ Example::
859
+
860
+ result = await fleet.execute_verifier_local(
861
+ verifier_func=verifier_code_string,
862
+ seed_db="./seed.db",
863
+ current_db="./current.db",
864
+ )
865
+ print(result["result"]) # 1 (TASK_SUCCESSFUL_SCORE) or 0
866
+ """
867
+ import asyncio
868
+ from ..verifiers.local_executor import execute_verifier_local
869
+
870
+ return await asyncio.to_thread(
871
+ execute_verifier_local, verifier_func, seed_db, current_db, final_answer
872
+ )
873
+
874
+ @staticmethod
875
+ async def diff_dbs(
876
+ seed_db: str,
877
+ current_db: str,
878
+ ignore_tables: Optional[set] = None,
879
+ ignore_table_fields: Optional[Dict[str, set]] = None,
880
+ ) -> Dict[str, Any]:
881
+ """Compute a structured diff between two local SQLite databases.
882
+
883
+ Returns the same format as the runner's ``/diff/structured`` endpoint.
884
+ No authentication or network access required.
885
+
886
+ Args:
887
+ seed_db: Path to the seed (before) SQLite database file.
888
+ current_db: Path to the current (after) SQLite database file.
889
+ ignore_tables: Optional set of table names to skip entirely.
890
+ ignore_table_fields: Optional mapping of ``{table: {field, ...}}``
891
+ to strip from the output.
892
+
893
+ Returns:
894
+ Dict with keys ``success``, ``diff``, and ``message``.
895
+ """
896
+ import asyncio
897
+ from ..verifiers.local_executor import diff_dbs
898
+
899
+ return await asyncio.to_thread(
900
+ diff_dbs, seed_db, current_db, ignore_tables, ignore_table_fields
901
+ )
902
+
836
903
  async def list_runs(
837
904
  self, profile_id: Optional[str] = None, status: Optional[str] = "active"
838
905
  ) -> List[Run]:
@@ -27,7 +27,7 @@ from .exceptions import (
27
27
  try:
28
28
  from . import __version__
29
29
  except ImportError:
30
- __version__ = "0.2.116"
30
+ __version__ = "0.2.118"
31
31
 
32
32
  logger = logging.getLogger(__name__)
33
33
 
@@ -844,7 +844,68 @@ class Fleet:
844
844
  At least one of run_id or profile_id must be provided.
845
845
  """
846
846
  return _delete_instances_batch(self.client, run_id=run_id, profile_id=profile_id)
847
-
847
+
848
+ @staticmethod
849
+ def execute_verifier_local(
850
+ verifier_func: str,
851
+ seed_db: str,
852
+ current_db: str,
853
+ final_answer: Optional[str] = None,
854
+ ) -> Dict[str, Any]:
855
+ """Execute a verifier function locally against SQLite database files.
856
+
857
+ No authentication or remote server required. The verifier code is executed
858
+ in an isolated namespace with the same helpers available in production
859
+ (``normalized_contains``, ``IgnoreConfig``, ``DatabaseSnapshot``, etc.).
860
+
861
+ Args:
862
+ verifier_func: Python source code containing the verifier function definition.
863
+ seed_db: Path to the seed (before) SQLite database file.
864
+ current_db: Path to the current (after) SQLite database file.
865
+ final_answer: Optional final answer string passed to the verifier.
866
+
867
+ Returns:
868
+ Dict with keys ``success``, ``result``, ``error``, and ``stdout``.
869
+
870
+ Example::
871
+
872
+ result = fleet.execute_verifier_local(
873
+ verifier_func=verifier_code_string,
874
+ seed_db="./seed.db",
875
+ current_db="./current.db",
876
+ )
877
+ print(result["result"]) # 1 (TASK_SUCCESSFUL_SCORE) or 0
878
+ """
879
+ from .verifiers.local_executor import execute_verifier_local
880
+
881
+ return execute_verifier_local(verifier_func, seed_db, current_db, final_answer)
882
+
883
+ @staticmethod
884
+ def diff_dbs(
885
+ seed_db: str,
886
+ current_db: str,
887
+ ignore_tables: Optional[set] = None,
888
+ ignore_table_fields: Optional[Dict[str, set]] = None,
889
+ ) -> Dict[str, Any]:
890
+ """Compute a structured diff between two local SQLite databases.
891
+
892
+ Returns the same format as the runner's ``/diff/structured`` endpoint.
893
+ No authentication or network access required.
894
+
895
+ Args:
896
+ seed_db: Path to the seed (before) SQLite database file.
897
+ current_db: Path to the current (after) SQLite database file.
898
+ ignore_tables: Optional set of table names to skip entirely.
899
+ ignore_table_fields: Optional mapping of ``{table: {field, ...}}``
900
+ to strip from the output.
901
+
902
+ Returns:
903
+ Dict with keys ``success``, ``diff``, and ``message``.
904
+ """
905
+ from .verifiers.local_executor import diff_dbs
906
+
907
+ return diff_dbs(seed_db, current_db, ignore_tables, ignore_table_fields)
908
+
848
909
  def list_runs(
849
910
  self, profile_id: Optional[str] = None, status: Optional[str] = "active"
850
911
  ) -> List[Run]:
@@ -6,6 +6,7 @@ from .verifier import (
6
6
  verifier,
7
7
  SyncVerifierFunction,
8
8
  )
9
+ from .local_executor import execute_verifier_local, LocalEnvironment, diff_dbs
9
10
 
10
11
  __all__ = [
11
12
  "DatabaseSnapshot",
@@ -15,4 +16,7 @@ __all__ = [
15
16
  "TASK_FAILED_SCORE",
16
17
  "verifier",
17
18
  "SyncVerifierFunction",
19
+ "execute_verifier_local",
20
+ "LocalEnvironment",
21
+ "diff_dbs",
18
22
  ]
@@ -0,0 +1,371 @@
1
+ """Local verifier execution and database diffing against SQLite files.
2
+
3
+ Executes verifier function code directly against local SQLite database files
4
+ and computes structured diffs, without requiring authentication or a remote
5
+ runner API server.
6
+ """
7
+
8
+ import inspect
9
+ import json
10
+ import re
11
+ import string
12
+ import traceback
13
+ from io import StringIO
14
+ from typing import Any, Dict, Optional
15
+
16
+ from .db import DatabaseSnapshot, IgnoreConfig, SnapshotDiff
17
+ from .code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Helper functions injected into verifier execution namespace
22
+ # ---------------------------------------------------------------------------
23
+
24
+ _TRANSLATOR = str.maketrans(string.punctuation, " " * len(string.punctuation))
25
+
26
+
27
+ def _normalize_text(value: str) -> str:
28
+ text = value.lower().translate(_TRANSLATOR)
29
+ return "".join(text.split())
30
+
31
+
32
+ def _stringify_content(content: Any) -> str:
33
+ if isinstance(content, (dict, list)):
34
+ return json.dumps(content, sort_keys=True)
35
+ return str(content)
36
+
37
+
38
+ def normalized_contains(target: str, blob: Any) -> bool:
39
+ """Check if target is contained in blob after normalising punctuation and case."""
40
+ normalized_target = _normalize_text(target)
41
+ normalized_blob = _normalize_text(_stringify_content(blob))
42
+ return normalized_target in normalized_blob
43
+
44
+
45
+ def normalized_string_comparison(target: str, blob: Any) -> bool:
46
+ """Check if target equals blob after normalising punctuation and case."""
47
+ normalized_target = _normalize_text(target)
48
+ normalized_blob = _normalize_text(_stringify_content(blob))
49
+ return normalized_target == normalized_blob
50
+
51
+
52
+ def extract_numbers(text: str) -> list:
53
+ """Extract all numbers from a string."""
54
+ cleaned_text = text.replace(",", "")
55
+ pattern = r"-?\d+\.?\d*"
56
+ matches = re.findall(pattern, cleaned_text)
57
+ return [float(num) for num in matches]
58
+
59
+
60
+ def contains_number(text: str, target_number) -> bool:
61
+ """Check if text contains the target number."""
62
+ numbers = extract_numbers(text)
63
+ try:
64
+ if isinstance(target_number, str):
65
+ target_number = target_number.replace(",", "")
66
+ target = float(target_number)
67
+ except (ValueError, AttributeError):
68
+ return False
69
+ return target in numbers
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Lightweight Environment mock for local verifier execution
74
+ # ---------------------------------------------------------------------------
75
+
76
+ class _LocalInstance:
77
+ """Mock instance that supports load() as a no-op."""
78
+
79
+ def load(self):
80
+ pass
81
+
82
+
83
+ class LocalEnvironment:
84
+ """Lightweight environment that wraps local SQLite files for verifier execution.
85
+
86
+ Provides the same interface verifier functions expect from ``env``:
87
+ ``env.db("seed")``, ``env.db("current")``, and ``env.instance.load()``.
88
+ """
89
+
90
+ def __init__(self, seed_db: str, current_db: str):
91
+ self._snapshots: Dict[str, DatabaseSnapshot] = {
92
+ "seed": DatabaseSnapshot(seed_db, name="seed"),
93
+ "current": DatabaseSnapshot(current_db, name="current"),
94
+ }
95
+ self.instance = _LocalInstance()
96
+
97
+ def db(self, name: str = "current") -> DatabaseSnapshot:
98
+ if name not in self._snapshots:
99
+ raise KeyError(
100
+ f"Unknown database '{name}'. Available: {list(self._snapshots.keys())}"
101
+ )
102
+ return self._snapshots[name]
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Core execution function
107
+ # ---------------------------------------------------------------------------
108
+
109
+ def execute_verifier_local(
110
+ verifier_func: str,
111
+ seed_db: str,
112
+ current_db: str,
113
+ final_answer: Optional[str] = None,
114
+ ) -> Dict[str, Any]:
115
+ """Execute a verifier function string locally against SQLite database files.
116
+
117
+ No authentication or remote server required. The function is executed in an
118
+ isolated namespace with the same helpers available to production verifiers.
119
+
120
+ Args:
121
+ verifier_func: Python source code containing the verifier function definition.
122
+ seed_db: Path to the seed (before) SQLite database file.
123
+ current_db: Path to the current (after) SQLite database file.
124
+ final_answer: Optional final answer string passed to the verifier.
125
+
126
+ Returns:
127
+ Dict with keys:
128
+ - ``success`` (bool): Whether execution completed without errors.
129
+ - ``result`` (Any): The return value of the verifier function (typically a score).
130
+ - ``error`` (str | None): Error message and traceback if execution failed.
131
+ - ``stdout`` (str): Captured stdout output from the verifier function.
132
+ """
133
+ import sys
134
+
135
+ # Capture stdout
136
+ captured_stdout = StringIO()
137
+
138
+ try:
139
+ # Build the local environment
140
+ env = LocalEnvironment(seed_db, current_db)
141
+
142
+ # Clean the verifier code – strip decorators and fleet imports
143
+ cleaned_code = re.sub(r"@verifier\([^)]*\)\s*\n", "", verifier_func)
144
+ cleaned_code = re.sub(
145
+ r"^from fleet\.verifiers.*import.*$\n?",
146
+ "",
147
+ cleaned_code,
148
+ flags=re.MULTILINE,
149
+ )
150
+ cleaned_code = re.sub(
151
+ r"^from fleet import verifier.*$\n?",
152
+ "",
153
+ cleaned_code,
154
+ flags=re.MULTILINE,
155
+ )
156
+ cleaned_code = re.sub(
157
+ r"^import fleet\.verifiers.*$\n?",
158
+ "",
159
+ cleaned_code,
160
+ flags=re.MULTILINE,
161
+ )
162
+ cleaned_code = re.sub(
163
+ r"^import fleet$\n?", "", cleaned_code, flags=re.MULTILINE
164
+ )
165
+
166
+ # Build execution namespace with all helpers available to verifiers
167
+ exec_globals: Dict[str, Any] = {
168
+ # Score constants
169
+ "TASK_SUCCESSFUL_SCORE": TASK_SUCCESSFUL_SCORE,
170
+ "TASK_FAILED_SCORE": TASK_FAILED_SCORE,
171
+ # Helper functions
172
+ "normalized_contains": normalized_contains,
173
+ "normalized_string_comparison": normalized_string_comparison,
174
+ "extract_numbers": extract_numbers,
175
+ "contains_number": contains_number,
176
+ # Database classes
177
+ "DatabaseSnapshot": DatabaseSnapshot,
178
+ "IgnoreConfig": IgnoreConfig,
179
+ "SnapshotDiff": SnapshotDiff,
180
+ # Environment type hint (not enforced at runtime)
181
+ "Environment": type(env),
182
+ # Standard library modules commonly used in verifiers
183
+ "json": json,
184
+ "re": re,
185
+ "string": string,
186
+ # Builtins
187
+ "__builtins__": __builtins__,
188
+ }
189
+
190
+ # Execute the verifier code to define the function(s)
191
+ local_namespace: Dict[str, Any] = {}
192
+ exec(cleaned_code, exec_globals, local_namespace)
193
+
194
+ # Merge so helper functions defined in verifier code are accessible
195
+ exec_globals.update(local_namespace)
196
+
197
+ # Find the verifier function (the one defined in user code)
198
+ func_obj = None
199
+ for name, obj in local_namespace.items():
200
+ if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
201
+ func_obj = obj
202
+ break
203
+
204
+ if func_obj is None:
205
+ return {
206
+ "success": False,
207
+ "result": None,
208
+ "error": "No function found in verifier code",
209
+ "stdout": "",
210
+ }
211
+
212
+ # Redirect stdout to capture print() output from verifiers
213
+ old_stdout = sys.stdout
214
+ sys.stdout = captured_stdout
215
+
216
+ try:
217
+ # Execute the verifier – verifiers take (env, final_answer=None)
218
+ sig = inspect.signature(func_obj)
219
+ params = list(sig.parameters.values())
220
+
221
+ if len(params) >= 2:
222
+ result = func_obj(env, final_answer)
223
+ elif len(params) == 1:
224
+ result = func_obj(env)
225
+ else:
226
+ result = func_obj()
227
+ finally:
228
+ sys.stdout = old_stdout
229
+
230
+ return {
231
+ "success": True,
232
+ "result": result,
233
+ "error": None,
234
+ "stdout": captured_stdout.getvalue(),
235
+ }
236
+
237
+ except Exception as e:
238
+ # Restore stdout if it was redirected
239
+ if sys.stdout is not sys.__stdout__ and sys.stdout is captured_stdout:
240
+ sys.stdout = sys.__stdout__
241
+
242
+ error_msg = f"{type(e).__name__}: {e}\n{traceback.format_exc()}"
243
+ return {
244
+ "success": False,
245
+ "result": None,
246
+ "error": error_msg,
247
+ "stdout": captured_stdout.getvalue(),
248
+ }
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # Structured database diff (matches /diff/structured response format)
253
+ # ---------------------------------------------------------------------------
254
+
255
+ def diff_dbs(
256
+ seed_db: str,
257
+ current_db: str,
258
+ ignore_tables: Optional[set] = None,
259
+ ignore_table_fields: Optional[dict] = None,
260
+ ) -> Dict[str, Any]:
261
+ """Compute a structured diff between two SQLite databases locally.
262
+
263
+ Returns the exact same format as the runner's ``/diff/structured`` endpoint:
264
+
265
+ .. code-block:: python
266
+
267
+ {
268
+ "success": True,
269
+ "diff": {
270
+ "table_name": {
271
+ "table_name": str,
272
+ "primary_key": [str],
273
+ "added_rows": [{"row_id": ..., "data": {...}}],
274
+ "removed_rows": [{"row_id": ..., "data": {...}}],
275
+ "modified_rows": [{"row_id": ..., "changes": {...}, "data": {...}}],
276
+ "unchanged_count": int,
277
+ "total_changes": int,
278
+ }
279
+ },
280
+ "message": str,
281
+ }
282
+
283
+ No authentication or network access required.
284
+
285
+ Args:
286
+ seed_db: Path to the seed (before) SQLite database file.
287
+ current_db: Path to the current (after) SQLite database file.
288
+ ignore_tables: Optional set of table names to skip entirely.
289
+ ignore_table_fields: Optional mapping of ``{table_name: {field, ...}}``
290
+ whose fields are stripped from the diff output.
291
+
292
+ Returns:
293
+ Dict matching the ``StructuredDiffResponse`` schema.
294
+ """
295
+ from .sql_differ import SQLiteDiffer
296
+
297
+ ignore_tables = ignore_tables or set()
298
+ ignore_table_fields = ignore_table_fields or {}
299
+
300
+ try:
301
+ differ = SQLiteDiffer(seed_db, current_db)
302
+ raw_diff = differ.diff_all_tables()
303
+
304
+ filtered_diff: Dict[str, Any] = {}
305
+ for table_name, table_diff in raw_diff.items():
306
+ if table_name in ignore_tables:
307
+ continue
308
+
309
+ # Skip tables that errored during diffing
310
+ if "error" in table_diff:
311
+ continue
312
+
313
+ ignored_fields = ignore_table_fields.get(table_name, set())
314
+
315
+ # Added rows
316
+ filtered_added = []
317
+ for row in table_diff.get("added_rows", []):
318
+ filtered_data = {
319
+ k: v for k, v in row["data"].items() if k not in ignored_fields
320
+ }
321
+ filtered_added.append({"row_id": row["row_id"], "data": filtered_data})
322
+
323
+ # Removed rows
324
+ filtered_removed = []
325
+ for row in table_diff.get("removed_rows", []):
326
+ filtered_data = {
327
+ k: v for k, v in row["data"].items() if k not in ignored_fields
328
+ }
329
+ filtered_removed.append({"row_id": row["row_id"], "data": filtered_data})
330
+
331
+ # Modified rows
332
+ filtered_modified = []
333
+ for row in table_diff.get("modified_rows", []):
334
+ filtered_changes = {
335
+ k: v for k, v in row["changes"].items() if k not in ignored_fields
336
+ }
337
+ if filtered_changes:
338
+ after_row = row.get("after_row", {})
339
+ filtered_data = {
340
+ k: v for k, v in after_row.items() if k not in ignored_fields
341
+ }
342
+ filtered_modified.append({
343
+ "row_id": row["row_id"],
344
+ "changes": filtered_changes,
345
+ "data": filtered_data,
346
+ })
347
+
348
+ total_changes = len(filtered_added) + len(filtered_removed) + len(filtered_modified)
349
+
350
+ filtered_diff[table_name] = {
351
+ "table_name": table_name,
352
+ "primary_key": table_diff.get("primary_key", []),
353
+ "added_rows": filtered_added,
354
+ "removed_rows": filtered_removed,
355
+ "modified_rows": filtered_modified,
356
+ "unchanged_count": table_diff.get("unchanged_count", 0),
357
+ "total_changes": total_changes,
358
+ }
359
+
360
+ return {
361
+ "success": True,
362
+ "diff": filtered_diff,
363
+ "message": "Structured diff generated successfully",
364
+ }
365
+
366
+ except Exception as e:
367
+ return {
368
+ "success": False,
369
+ "diff": {},
370
+ "message": f"Failed to generate structured diff: {e}",
371
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.116
3
+ Version: 0.2.118
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -101,6 +101,7 @@ fleet/verifiers/bundler.py
101
101
  fleet/verifiers/code.py
102
102
  fleet/verifiers/db.py
103
103
  fleet/verifiers/decorator.py
104
+ fleet/verifiers/local_executor.py
104
105
  fleet/verifiers/parse.py
105
106
  fleet/verifiers/sql_differ.py
106
107
  fleet/verifiers/verifier.py
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "fleet-python"
7
7
 
8
- version = "0.2.116"
8
+ version = "0.2.118"
9
9
  description = "Python SDK for Fleet environments"
10
10
  authors = [
11
11
  {name = "Fleet AI", email = "nic@fleet.so"},
File without changes
File without changes
File without changes