fleet-python 0.2.102__tar.gz → 0.2.103__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 (120) hide show
  1. {fleet_python-0.2.102/fleet_python.egg-info → fleet_python-0.2.103}/PKG-INFO +1 -1
  2. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/export_tasks.py +5 -4
  3. fleet_python-0.2.103/examples/export_tasks_filtered.py +245 -0
  4. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/import_tasks.py +130 -13
  5. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/__init__.py +1 -1
  6. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/__init__.py +1 -1
  7. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/base.py +1 -1
  8. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/client.py +5 -0
  9. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/base.py +1 -1
  10. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/client.py +5 -0
  11. {fleet_python-0.2.102 → fleet_python-0.2.103/fleet_python.egg-info}/PKG-INFO +1 -1
  12. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet_python.egg-info/SOURCES.txt +1 -0
  13. {fleet_python-0.2.102 → fleet_python-0.2.103}/pyproject.toml +1 -1
  14. {fleet_python-0.2.102 → fleet_python-0.2.103}/LICENSE +0 -0
  15. {fleet_python-0.2.102 → fleet_python-0.2.103}/README.md +0 -0
  16. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/diff_example.py +0 -0
  17. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/dsl_example.py +0 -0
  18. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example.py +0 -0
  19. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/exampleResume.py +0 -0
  20. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_account.py +0 -0
  21. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_action_log.py +0 -0
  22. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_client.py +0 -0
  23. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_mcp_anthropic.py +0 -0
  24. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_mcp_openai.py +0 -0
  25. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_sync.py +0 -0
  26. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_task.py +0 -0
  27. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_tasks.py +0 -0
  28. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/example_verifier.py +0 -0
  29. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/fetch_tasks.py +0 -0
  30. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/gemini_example.py +0 -0
  31. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/iterate_verifiers.py +0 -0
  32. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/json_tasks_example.py +0 -0
  33. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/nova_act_example.py +0 -0
  34. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/openai_example.py +0 -0
  35. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/openai_simple_example.py +0 -0
  36. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/query_builder_example.py +0 -0
  37. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/quickstart.py +0 -0
  38. {fleet_python-0.2.102 → fleet_python-0.2.103}/examples/test_cdp_logging.py +0 -0
  39. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/env/__init__.py +0 -0
  40. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/env/client.py +0 -0
  41. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/exceptions.py +0 -0
  42. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/global_client.py +0 -0
  43. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/instance/__init__.py +0 -0
  44. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/instance/base.py +0 -0
  45. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/instance/client.py +0 -0
  46. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/models.py +0 -0
  47. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/__init__.py +0 -0
  48. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/api.py +0 -0
  49. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/base.py +0 -0
  50. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/browser.py +0 -0
  51. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/mcp.py +0 -0
  52. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/resources/sqlite.py +0 -0
  53. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/tasks.py +0 -0
  54. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/verifiers/__init__.py +0 -0
  55. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/verifiers/bundler.py +0 -0
  56. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/_async/verifiers/verifier.py +0 -0
  57. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/__init__.py +0 -0
  58. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/Dockerfile +0 -0
  59. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/__init__.py +0 -0
  60. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/agent.py +0 -0
  61. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/mcp/main.py +0 -0
  62. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/mcp_server/__init__.py +0 -0
  63. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/mcp_server/main.py +0 -0
  64. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/mcp_server/tools.py +0 -0
  65. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/requirements.txt +0 -0
  66. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/gemini_cua/start.sh +0 -0
  67. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/orchestrator.py +0 -0
  68. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/types.py +0 -0
  69. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/agent/utils.py +0 -0
  70. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/cli.py +0 -0
  71. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/config.py +0 -0
  72. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/env/__init__.py +0 -0
  73. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/env/client.py +0 -0
  74. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/eval/__init__.py +0 -0
  75. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/eval/uploader.py +0 -0
  76. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/exceptions.py +0 -0
  77. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/global_client.py +0 -0
  78. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/instance/__init__.py +0 -0
  79. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/instance/base.py +0 -0
  80. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/instance/client.py +0 -0
  81. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/instance/models.py +0 -0
  82. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/models.py +0 -0
  83. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/proxy/__init__.py +0 -0
  84. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/proxy/proxy.py +0 -0
  85. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/proxy/whitelist.py +0 -0
  86. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/__init__.py +0 -0
  87. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/api.py +0 -0
  88. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/base.py +0 -0
  89. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/browser.py +0 -0
  90. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/mcp.py +0 -0
  91. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/resources/sqlite.py +0 -0
  92. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/tasks.py +0 -0
  93. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/types.py +0 -0
  94. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/utils/__init__.py +0 -0
  95. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/utils/http_logging.py +0 -0
  96. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/utils/logging.py +0 -0
  97. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/utils/playwright.py +0 -0
  98. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/__init__.py +0 -0
  99. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/bundler.py +0 -0
  100. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/code.py +0 -0
  101. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/db.py +0 -0
  102. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/decorator.py +0 -0
  103. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/parse.py +0 -0
  104. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/sql_differ.py +0 -0
  105. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet/verifiers/verifier.py +0 -0
  106. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet_python.egg-info/dependency_links.txt +0 -0
  107. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet_python.egg-info/entry_points.txt +0 -0
  108. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet_python.egg-info/requires.txt +0 -0
  109. {fleet_python-0.2.102 → fleet_python-0.2.103}/fleet_python.egg-info/top_level.txt +0 -0
  110. {fleet_python-0.2.102 → fleet_python-0.2.103}/scripts/fix_sync_imports.py +0 -0
  111. {fleet_python-0.2.102 → fleet_python-0.2.103}/scripts/unasync.py +0 -0
  112. {fleet_python-0.2.102 → fleet_python-0.2.103}/setup.cfg +0 -0
  113. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/__init__.py +0 -0
  114. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_app_method.py +0 -0
  115. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_expect_exactly.py +0 -0
  116. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_expect_only.py +0 -0
  117. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_instance_dispatch.py +0 -0
  118. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_sqlite_resource_dual_mode.py +0 -0
  119. {fleet_python-0.2.102 → fleet_python-0.2.103}/tests/test_sqlite_shared_memory_behavior.py +0 -0
  120. {fleet_python-0.2.102 → fleet_python-0.2.103}/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.102
3
+ Version: 0.2.103
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -17,8 +17,7 @@ def main():
17
17
  parser.add_argument(
18
18
  "--task-keys",
19
19
  "-t",
20
- nargs="+",
21
- help="Optional list of task keys to export (space-separated)",
20
+ help="Optional list of task keys to export (comma-separated)",
22
21
  default=None,
23
22
  )
24
23
  parser.add_argument(
@@ -66,10 +65,12 @@ def main():
66
65
  print(f"Loading tasks from project: {args.project_key}")
67
66
  tasks = fleet.load_tasks(project_key=args.project_key)
68
67
  elif args.task_keys:
68
+ # Split comma-separated task keys and strip whitespace
69
+ task_keys_list = [key.strip() for key in args.task_keys.split(",")]
69
70
  print(
70
- f"Loading {len(args.task_keys)} specific task(s): {', '.join(args.task_keys)}"
71
+ f"Loading {len(task_keys_list)} specific task(s): {', '.join(task_keys_list)}"
71
72
  )
72
- tasks = fleet.load_tasks(keys=args.task_keys)
73
+ tasks = fleet.load_tasks(keys=task_keys_list)
73
74
  elif args.task_project_key:
74
75
  print(f"Loading tasks from task project: {args.task_project_key}")
75
76
  tasks = fleet.load_tasks(task_project_key=args.task_project_key)
@@ -0,0 +1,245 @@
1
+ """
2
+ Export tasks to JSON, excluding tasks from targets marked as 'unused'.
3
+
4
+ This script filters out tasks whose task_project_target has status='unused',
5
+ ensuring that broken/invalid targets don't pollute exports.
6
+
7
+ Usage:
8
+ python export_tasks_filtered.py --task-project-key my-project
9
+ python export_tasks_filtered.py --project-key my-project --output tasks.json
10
+ python export_tasks_filtered.py --env-key my-env
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import os
16
+ from typing import List, Set
17
+
18
+ import fleet
19
+ from dotenv import load_dotenv
20
+ from supabase import create_client, Client
21
+
22
+ load_dotenv()
23
+
24
+
25
+ def get_unused_target_ids(supabase: Client, team_id: str) -> Set[str]:
26
+ """Fetch all target IDs that have status='unused' for the given team."""
27
+ # Get all task_projects for this team first
28
+ projects_response = (
29
+ supabase.table("task_projects")
30
+ .select("id")
31
+ .eq("team_id", team_id)
32
+ .execute()
33
+ )
34
+
35
+ if not projects_response.data:
36
+ return set()
37
+
38
+ project_ids = [p["id"] for p in projects_response.data]
39
+
40
+ # Get all targets with status='unused' for these projects
41
+ targets_response = (
42
+ supabase.table("task_project_targets")
43
+ .select("id")
44
+ .in_("project_id", project_ids)
45
+ .eq("status", "unused")
46
+ .execute()
47
+ )
48
+
49
+ if not targets_response.data:
50
+ return set()
51
+
52
+ return {t["id"] for t in targets_response.data}
53
+
54
+
55
+ def get_task_target_mapping(supabase: Client, task_keys: List[str], team_id: str) -> dict:
56
+ """Fetch task_project_target_id for each task key."""
57
+ if not task_keys:
58
+ return {}
59
+
60
+ # Batch the queries to avoid hitting limits
61
+ BATCH_SIZE = 100
62
+ mapping = {}
63
+
64
+ for i in range(0, len(task_keys), BATCH_SIZE):
65
+ batch_keys = task_keys[i:i + BATCH_SIZE]
66
+ response = (
67
+ supabase.table("eval_tasks")
68
+ .select("key, task_project_target_id")
69
+ .in_("key", batch_keys)
70
+ .eq("team_id", team_id)
71
+ .execute()
72
+ )
73
+
74
+ for row in response.data or []:
75
+ mapping[row["key"]] = row.get("task_project_target_id")
76
+
77
+ return mapping
78
+
79
+
80
+ def main():
81
+ parser = argparse.ArgumentParser(
82
+ description="Export tasks to JSON, excluding tasks from 'unused' targets"
83
+ )
84
+ parser.add_argument(
85
+ "--project-key",
86
+ "-p",
87
+ help="Optional project key to filter tasks",
88
+ default=None,
89
+ )
90
+ parser.add_argument(
91
+ "--task-keys",
92
+ "-t",
93
+ nargs="+",
94
+ help="Optional list of task keys to export (space-separated)",
95
+ default=None,
96
+ )
97
+ parser.add_argument(
98
+ "--task-project-key",
99
+ "-tpk",
100
+ help="Optional task project key to filter tasks",
101
+ default=None,
102
+ )
103
+ parser.add_argument(
104
+ "--env-key",
105
+ "-e",
106
+ help="Optional environment key to filter tasks",
107
+ default=None,
108
+ )
109
+ parser.add_argument(
110
+ "--output",
111
+ "-o",
112
+ help="Output JSON filename (defaults to {team_id}_filtered.json)",
113
+ default=None,
114
+ )
115
+ parser.add_argument(
116
+ "--include-unused",
117
+ action="store_true",
118
+ help="Include tasks from 'unused' targets (disables filtering)",
119
+ )
120
+
121
+ args = parser.parse_args()
122
+
123
+ # Validate that only one filter is specified
124
+ filters_specified = sum(
125
+ [
126
+ args.project_key is not None,
127
+ args.task_keys is not None,
128
+ args.task_project_key is not None,
129
+ args.env_key is not None,
130
+ ]
131
+ )
132
+
133
+ if filters_specified > 1:
134
+ parser.error(
135
+ "Cannot specify multiple filters. Use only one of --project-key, --task-keys, --task-project-key, or --env-key."
136
+ )
137
+
138
+ # Get account info
139
+ account = fleet.env.account()
140
+ print(f"Exporting from team: {account.team_name}")
141
+
142
+ # Initialize Supabase client for filtering
143
+ supabase_url = os.getenv("SUPABASE_URL") or os.getenv("NEXT_PUBLIC_SUPABASE_URL")
144
+ supabase_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY") or os.getenv("SUPABASE_KEY")
145
+
146
+ if not supabase_url or not supabase_key:
147
+ print("⚠ Warning: SUPABASE_URL/SUPABASE_KEY not set - cannot filter by target status")
148
+ print(" Falling back to unfiltered export")
149
+ supabase = None
150
+ else:
151
+ supabase = create_client(supabase_url, supabase_key)
152
+
153
+ # Load tasks
154
+ if args.project_key:
155
+ print(f"Loading tasks from project: {args.project_key}")
156
+ tasks = fleet.load_tasks(project_key=args.project_key)
157
+ elif args.task_keys:
158
+ print(f"Loading {len(args.task_keys)} specific task(s): {', '.join(args.task_keys)}")
159
+ tasks = fleet.load_tasks(keys=args.task_keys)
160
+ elif args.task_project_key:
161
+ print(f"Loading tasks from task project: {args.task_project_key}")
162
+ tasks = fleet.load_tasks(task_project_key=args.task_project_key)
163
+ elif args.env_key:
164
+ print(f"Loading tasks from environment: {args.env_key}")
165
+ tasks = fleet.load_tasks(env_key=args.env_key)
166
+ else:
167
+ print("Loading all tasks")
168
+ tasks = fleet.load_tasks()
169
+
170
+ print(f"\nFound {len(tasks)} task(s) before filtering")
171
+
172
+ # Filter out tasks from unused targets
173
+ filtered_tasks = tasks
174
+ excluded_count = 0
175
+
176
+ if supabase and not args.include_unused:
177
+ print("\nFiltering out tasks from 'unused' targets...")
178
+
179
+ # Get unused target IDs
180
+ unused_target_ids = get_unused_target_ids(supabase, account.team_id)
181
+
182
+ if unused_target_ids:
183
+ print(f" Found {len(unused_target_ids)} unused target(s)")
184
+
185
+ # Get task -> target mapping
186
+ task_keys = [t.key for t in tasks]
187
+ task_target_map = get_task_target_mapping(supabase, task_keys, account.team_id)
188
+
189
+ # Filter tasks
190
+ filtered_tasks = []
191
+ for task in tasks:
192
+ target_id = task_target_map.get(task.key)
193
+ if target_id in unused_target_ids:
194
+ excluded_count += 1
195
+ else:
196
+ filtered_tasks.append(task)
197
+
198
+ print(f" Excluded {excluded_count} task(s) from unused targets")
199
+ else:
200
+ print(" No unused targets found - all tasks included")
201
+
202
+ tasks = filtered_tasks
203
+ print(f"\n{len(tasks)} task(s) after filtering")
204
+
205
+ # Validate that all tasks have verifier_func
206
+ print("\nValidating tasks have verifier_func...")
207
+ missing_verifier = []
208
+ for task in tasks:
209
+ if not task.verifier_func:
210
+ missing_verifier.append(task.key)
211
+
212
+ if missing_verifier:
213
+ print(f"\n✗ Error: {len(missing_verifier)} task(s) missing verifier_func:")
214
+ for key in missing_verifier[:10]: # Show first 10
215
+ print(f" - {key}")
216
+ if len(missing_verifier) > 10:
217
+ print(f" ... and {len(missing_verifier) - 10} more")
218
+ raise ValueError(
219
+ "All tasks must have a verifier_func. Cannot export tasks without verifiers."
220
+ )
221
+
222
+ print("✓ All tasks have verifier_func")
223
+
224
+ # Determine output filename
225
+ output_file = args.output or f"{account.team_id}_filtered.json"
226
+
227
+ # Export to JSON
228
+ print(f"\nExporting to: {output_file}")
229
+ with open(output_file, "w", encoding="utf-8") as f:
230
+ json.dump(
231
+ [task.model_dump() for task in tasks],
232
+ f,
233
+ indent=2,
234
+ ensure_ascii=False,
235
+ )
236
+
237
+ print(f"✓ Successfully exported {len(tasks)} task(s) to {output_file}")
238
+ if excluded_count > 0:
239
+ print(f" ({excluded_count} task(s) excluded from unused targets)")
240
+
241
+
242
+ if __name__ == "__main__":
243
+ main()
244
+
245
+
@@ -1,7 +1,9 @@
1
1
  import asyncio
2
2
  import argparse
3
3
  import json
4
+ import os
4
5
  import sys
6
+ import tempfile
5
7
  from collections import defaultdict
6
8
  from typing import Dict, List, Tuple
7
9
  import fleet
@@ -183,7 +185,6 @@ async def run_verifier_sanity_check(
183
185
  print(f" - {task_key}: {error_msg}")
184
186
  if len(errors) > 10:
185
187
  print(f" ... and {len(errors) - 10} more")
186
- print("\nFix the verifiers and try again.")
187
188
  return False, errors
188
189
  else:
189
190
  print("✓ All verifiers passed!")
@@ -237,6 +238,7 @@ async def main():
237
238
  task_count = len(tasks_data)
238
239
  task_keys = []
239
240
  missing_verifier = []
241
+ tasks_with_output_schema = []
240
242
  for task_data in tasks_data:
241
243
  task_key = task_data.get("key") or task_data.get("id")
242
244
  if task_key:
@@ -249,6 +251,10 @@ async def main():
249
251
  if not verifier_code:
250
252
  missing_verifier.append(task_key or "(no key)")
251
253
 
254
+ # Check for output_json_schema
255
+ if task_data.get("output_json_schema"):
256
+ tasks_with_output_schema.append(task_key or "(no key)")
257
+
252
258
  # Validate all tasks have verifier_func
253
259
  if missing_verifier:
254
260
  print(f"✗ Error: {len(missing_verifier)} task(s) missing verifier_func:")
@@ -291,21 +297,64 @@ async def main():
291
297
  print(f"✓ Loaded {len(tasks)} tasks")
292
298
 
293
299
  # Run sanity check (unless skipped)
300
+ already_confirmed = False # Track if user already confirmed import
294
301
  if not args.skip_sanity_check:
295
302
  success, errors = await run_verifier_sanity_check(tasks, client)
296
- if not success:
297
- sys.exit(1)
298
303
 
299
- # If only doing sanity check, exit successfully here
304
+ # If only doing sanity check, exit here
300
305
  if args.sanity_check_only:
301
- print("\n✓ Sanity check complete! (--sanity-check-only)")
302
- print("Tasks are ready to import.")
303
- sys.exit(0)
306
+ if success:
307
+ print("\n✓ Sanity check complete! (--sanity-check-only)")
308
+ print("Tasks are ready to import.")
309
+ sys.exit(0)
310
+ else:
311
+ print("\n✗ Sanity check failed (--sanity-check-only)")
312
+ print("Fix the verifiers and try again.")
313
+ sys.exit(1)
314
+
315
+ # Handle partial failures
316
+ if not success:
317
+ # Filter out failed tasks
318
+ failed_keys = set(errors.keys())
319
+ passed_tasks = [t for t in tasks if t.key not in failed_keys]
320
+
321
+ print("\n" + "=" * 60)
322
+ print("SANITY CHECK RESULTS")
323
+ print("=" * 60)
324
+ print(f"Passed: {len(passed_tasks)}/{len(tasks)} tasks")
325
+ print(f"Failed: {len(failed_keys)}/{len(tasks)} tasks")
326
+
327
+ if len(passed_tasks) == 0:
328
+ print("\n✗ No tasks passed the sanity check.")
329
+ print("Fix the verifiers and try again.")
330
+ sys.exit(1)
331
+
332
+ # Prompt user to import only passed tasks
333
+ if not args.yes:
334
+ print("\nWould you like to import only the tasks that passed?")
335
+ response = input("Type 'YES' to import passed tasks only: ")
336
+ if response != "YES":
337
+ print("Import cancelled.")
338
+ sys.exit(0)
339
+ already_confirmed = True # User already confirmed import
340
+ else:
341
+ print("\n⚠️ Auto-importing only tasks that passed (--yes flag)")
342
+
343
+ # Update tasks list and tasks_data to only include passed tasks
344
+ tasks = passed_tasks
345
+ passed_keys = {t.key for t in passed_tasks}
346
+ tasks_data = [td for td in tasks_data if td.get("key") in passed_keys]
347
+ # Also filter tasks_with_output_schema
348
+ tasks_with_output_schema = [
349
+ k for k in tasks_with_output_schema if k in passed_keys
350
+ ]
351
+
352
+ print(f"\nProceeding with {len(tasks)} tasks that passed sanity check")
304
353
  else:
305
354
  print("\n⚠️ Skipping sanity check (--skip-sanity-check)")
306
355
 
307
- # Confirmation prompt (unless --yes flag is provided)
308
- if not args.yes:
356
+ # Confirmation prompt (unless --yes flag is provided or already confirmed)
357
+ if not args.yes and not already_confirmed:
309
358
  print("\n" + "=" * 60)
310
359
  response = input("Type 'YES' to proceed with import: ")
311
360
  if response != "YES":
@@ -315,10 +364,78 @@ async def main():
315
364
  # Import tasks
316
365
  print("\nImporting tasks...")
317
366
  try:
318
- results = await fleet.import_tasks_async(
319
- args.json_file, project_key=args.project_key
320
- )
321
- print(f"\n✓ Successfully imported {len(results)} task(s)")
367
+ # If tasks were filtered, write to a temporary file for import
368
+ if len(tasks_data) < task_count:
369
+ # Create temporary file with filtered tasks
370
+ with tempfile.NamedTemporaryFile(
371
+ mode="w", suffix=".json", delete=False, encoding="utf-8"
372
+ ) as temp_file:
373
+ json.dump(tasks_data, temp_file, indent=2, ensure_ascii=False)
374
+ temp_filename = temp_file.name
375
+
376
+ try:
377
+ results = await fleet.import_tasks_async(
378
+ temp_filename, project_key=args.project_key
379
+ )
380
+ finally:
381
+ # Clean up temporary file
382
+ os.unlink(temp_filename)
383
+ else:
384
+ # Import from original file if no filtering occurred
385
+ results = await fleet.import_tasks_async(
386
+ args.json_file, project_key=args.project_key
387
+ )
388
+
389
+ # Print success summary
390
+ print("\n" + "=" * 60)
391
+ print("IMPORT COMPLETE")
392
+ print("=" * 60)
393
+ print(f"✓ Successfully imported {len(results)} task(s)")
394
+
395
+ if args.project_key:
396
+ print(f"✓ Associated with project: {args.project_key}")
397
+
398
+ print(f"✓ Team: {account.team_name}")
399
+
400
+ # Print HUGE warning if any tasks have output_json_schema
401
+ if tasks_with_output_schema:
402
+ print("\n")
403
+ print("!" * 80)
404
+ print("!" * 80)
405
+ print("!" * 80)
406
+ print(
407
+ "!!! !!!"
408
+ )
409
+ print(
410
+ "!!! ⚠️ WARNING WARNING WARNING ⚠️ !!!"
411
+ )
412
+ print(
413
+ "!!! !!!"
414
+ )
415
+ print(
416
+ f"!!! {len(tasks_with_output_schema)} TASK(S) HAVE OUTPUT_JSON_SCHEMA THAT NEED MANUAL COPYING! !!!"
417
+ )
418
+ print(
419
+ "!!! !!!"
420
+ )
421
+ print(
422
+ "!!! The output_json_schema field is NOT automatically imported! !!!"
423
+ )
424
+ print(
425
+ "!!! You MUST manually copy the output schemas to each task! !!!"
426
+ )
427
+ print(
428
+ "!!! !!!"
429
+ )
430
+ print("!" * 80)
431
+ print("!" * 80)
432
+ print("!" * 80)
433
+ print("\nTasks with output_json_schema:")
434
+ for i, key in enumerate(tasks_with_output_schema[:20], 1):
435
+ print(f" {i}. {key}")
436
+ if len(tasks_with_output_schema) > 20:
437
+ print(f" ... and {len(tasks_with_output_schema) - 20} more")
438
+ print("\n⚠️ REMEMBER TO MANUALLY COPY OUTPUT SCHEMAS! ⚠️\n")
322
439
  except Exception as e:
323
440
  print(f"\n✗ Error importing tasks: {e}")
324
441
  sys.exit(1)
@@ -73,7 +73,7 @@ from . import env
73
73
  from . import global_client as _global_client
74
74
  from ._async import global_client as _async_global_client
75
75
 
76
- __version__ = "0.2.102"
76
+ __version__ = "0.2.103"
77
77
 
78
78
  __all__ = [
79
79
  # Core classes
@@ -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.102"
47
+ __version__ = "0.2.103"
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.102"
29
+ __version__ = "0.2.103"
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
@@ -400,6 +400,11 @@ class AsyncEnv(EnvironmentBase):
400
400
  base_url = self.urls.api
401
401
  elif self.urls and self.urls.root:
402
402
  base_url = f"{self.urls.root.rstrip('/')}/raw"
403
+ elif self._manager_url_override and self._manager_url_override != "local://":
404
+ # URL mode: strip /api/v1/env suffix to get root URL
405
+ base_url = self._manager_url_override.rstrip('/')
406
+ if base_url.endswith('/api/v1/env'):
407
+ base_url = base_url[:-len('/api/v1/env')]
403
408
  else:
404
409
  raise ValueError("No API URL configured for this environment")
405
410
  return self.instance.api(name, base_url)
@@ -27,7 +27,7 @@ from .exceptions import (
27
27
  try:
28
28
  from . import __version__
29
29
  except ImportError:
30
- __version__ = "0.2.102"
30
+ __version__ = "0.2.103"
31
31
 
32
32
  logger = logging.getLogger(__name__)
33
33
 
@@ -412,6 +412,11 @@ class SyncEnv(EnvironmentBase):
412
412
  base_url = self.urls.api
413
413
  elif self.urls and self.urls.root:
414
414
  base_url = f"{self.urls.root.rstrip('/')}/raw"
415
+ elif self._manager_url_override and self._manager_url_override != "local://":
416
+ # URL mode: strip /api/v1/env suffix to get root URL
417
+ base_url = self._manager_url_override.rstrip('/')
418
+ if base_url.endswith('/api/v1/env'):
419
+ base_url = base_url[:-len('/api/v1/env')]
415
420
  else:
416
421
  raise ValueError("No API URL configured for this environment")
417
422
  return self.instance.api(name, base_url)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.102
3
+ Version: 0.2.103
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -15,6 +15,7 @@ examples/example_task.py
15
15
  examples/example_tasks.py
16
16
  examples/example_verifier.py
17
17
  examples/export_tasks.py
18
+ examples/export_tasks_filtered.py
18
19
  examples/fetch_tasks.py
19
20
  examples/gemini_example.py
20
21
  examples/import_tasks.py
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "fleet-python"
7
7
 
8
- version = "0.2.102"
8
+ version = "0.2.103"
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