fleet-python 0.2.12__py3-none-any.whl → 0.2.15__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 (51) hide show
  1. examples/diff_example.py +161 -0
  2. examples/dsl_example.py +50 -1
  3. examples/example.py +1 -1
  4. examples/example_action_log.py +28 -0
  5. examples/example_mcp_anthropic.py +77 -0
  6. examples/example_mcp_openai.py +27 -0
  7. examples/example_sync.py +1 -1
  8. examples/example_task.py +199 -0
  9. examples/example_verifier.py +71 -0
  10. examples/query_builder_example.py +117 -0
  11. fleet/__init__.py +51 -40
  12. fleet/_async/base.py +15 -2
  13. fleet/_async/client.py +141 -23
  14. fleet/_async/env/client.py +5 -5
  15. fleet/_async/instance/__init__.py +2 -3
  16. fleet/_async/instance/base.py +5 -2
  17. fleet/_async/instance/client.py +5 -4
  18. fleet/_async/playwright.py +2 -2
  19. fleet/_async/resources/base.py +1 -1
  20. fleet/_async/resources/browser.py +1 -1
  21. fleet/_async/resources/sqlite.py +656 -2
  22. fleet/_async/tasks.py +44 -0
  23. fleet/_async/verifiers/__init__.py +17 -0
  24. fleet/_async/verifiers/bundler.py +699 -0
  25. fleet/_async/verifiers/verifier.py +301 -0
  26. fleet/base.py +14 -1
  27. fleet/client.py +650 -17
  28. fleet/config.py +2 -1
  29. fleet/instance/__init__.py +1 -2
  30. fleet/instance/base.py +5 -2
  31. fleet/instance/client.py +16 -6
  32. fleet/models.py +171 -4
  33. fleet/resources/browser.py +7 -8
  34. fleet/resources/mcp.py +60 -0
  35. fleet/resources/sqlite.py +654 -0
  36. fleet/tasks.py +44 -0
  37. fleet/types.py +18 -0
  38. fleet/verifiers/__init__.py +11 -5
  39. fleet/verifiers/bundler.py +699 -0
  40. fleet/verifiers/decorator.py +103 -0
  41. fleet/verifiers/verifier.py +301 -0
  42. {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/METADATA +3 -42
  43. fleet_python-0.2.15.dist-info/RECORD +69 -0
  44. scripts/fix_sync_imports.py +30 -12
  45. fleet/_async/config.py +0 -8
  46. fleet/_async/instance/models.py +0 -141
  47. fleet/_async/models.py +0 -109
  48. fleet_python-0.2.12.dist-info/RECORD +0 -55
  49. {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/WHEEL +0 -0
  50. {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/licenses/LICENSE +0 -0
  51. {fleet_python-0.2.12.dist-info → fleet_python-0.2.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,161 @@
1
+ import asyncio
2
+ import fleet as flt
3
+ from fleet.verifiers import IgnoreConfig
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+
9
+ async def main():
10
+ # Create a new instance
11
+ print("Creating new Hubspot instance...")
12
+ env = await flt.env.make_async("hubspot:v1.2.7")
13
+ print(f"New Instance: {env.instance_id}")
14
+
15
+ try:
16
+ # Reset the instance
17
+ response = await env.reset(seed=42)
18
+ print(f"Reset response: {response}")
19
+
20
+ # Get the database resource
21
+ db = env.db()
22
+
23
+ # Take initial snapshot
24
+ print("\n=== Taking Initial Snapshot ===")
25
+ snapshot1 = await db.snapshot("initial_state")
26
+ print(f"Snapshot created: {snapshot1.name}")
27
+
28
+ # Show current entries
29
+ entries_count = await db.table("entries").count()
30
+ print(f"Initial entry count: {entries_count}")
31
+
32
+ # Make some changes
33
+ print("\n=== Making Database Changes ===")
34
+
35
+ # 1. Insert a new deal
36
+ print("\n1. Inserting new deal...")
37
+ await db.exec("""
38
+ INSERT INTO entries (id, name, type, owner_id, createdDate, lastModifiedDate, createdAt, updatedAt, properties)
39
+ VALUES (
40
+ 99001,
41
+ 'Test Deal 1',
42
+ 'deal',
43
+ 1,
44
+ datetime('now'),
45
+ datetime('now'),
46
+ datetime('now'),
47
+ datetime('now'),
48
+ '{}'
49
+ )
50
+ """)
51
+
52
+ # 2. Update an existing entry
53
+ print("2. Updating existing entry...")
54
+ await db.exec("""
55
+ UPDATE entries
56
+ SET name = 'Updated Contact Name'
57
+ WHERE id = 1
58
+ """)
59
+
60
+ # 3. Insert another entry
61
+ print("3. Inserting another deal...")
62
+ await db.exec("""
63
+ INSERT INTO entries (id, name, type, owner_id, createdDate, lastModifiedDate, createdAt, updatedAt, properties)
64
+ VALUES (
65
+ 99002,
66
+ 'Test Deal 2',
67
+ 'deal',
68
+ 1,
69
+ datetime('now'),
70
+ datetime('now'),
71
+ datetime('now'),
72
+ datetime('now'),
73
+ '{}'
74
+ )
75
+ """)
76
+
77
+ # Take second snapshot
78
+ print("\n=== Taking Second Snapshot ===")
79
+ snapshot2 = await db.snapshot("after_changes")
80
+ print(f"Snapshot created: {snapshot2.name}")
81
+
82
+ new_entries_count = await db.table("entries").count()
83
+ print(f"New entry count: {new_entries_count}")
84
+
85
+ # Compare snapshots
86
+ print("\n=== Comparing Snapshots ===")
87
+
88
+ # Configure what to ignore in diff
89
+ ignore_config = IgnoreConfig(
90
+ tables={"pageviews"}, # Ignore entire pageviews table
91
+ fields={"createdDate", "lastModifiedDate", "createdAt", "updatedAt"}, # Ignore timestamp fields
92
+ )
93
+
94
+ diff = await snapshot1.diff(snapshot2, ignore_config)
95
+
96
+ # Test 1: Validate all expected changes
97
+ print("\nTest 1: Validating expected changes...")
98
+ expected_changes = [
99
+ # New deals added
100
+ {"table": "entries", "pk": 99001, "field": None, "after": "__added__"},
101
+ {"table": "entries", "pk": 99002, "field": None, "after": "__added__"},
102
+ # Name updated
103
+ {"table": "entries", "pk": 1, "field": "name", "after": "Updated Contact Name"},
104
+ ]
105
+
106
+ try:
107
+ await diff.expect_only(expected_changes)
108
+ print("✓ All changes validated successfully!")
109
+ except AssertionError as e:
110
+ print(f"✗ Validation failed: {e}")
111
+
112
+ # Test 2: Try with incorrect expectations (should fail)
113
+ print("\nTest 2: Testing with incorrect expectations...")
114
+ incorrect_changes = [
115
+ {"table": "entries", "pk": 99001, "field": None, "after": "__added__"},
116
+ # Missing the second insert and the update
117
+ ]
118
+
119
+ try:
120
+ await diff.expect_only(incorrect_changes)
121
+ print("✗ This should have failed!")
122
+ except AssertionError as e:
123
+ print("✓ Correctly detected unexpected changes")
124
+ print(f" Error (first 200 chars): {str(e)[:200]}...")
125
+
126
+ # Test 3: Query snapshot data directly
127
+ print("\n=== Querying Snapshot Data ===")
128
+
129
+ # Query from first snapshot
130
+ print("\nQuerying from initial snapshot:")
131
+ initial_entry = await snapshot1.table("entries").eq("id", 1).first()
132
+ if initial_entry:
133
+ print(f"Entry 1 name in snapshot1: {initial_entry['name']}")
134
+
135
+ # Query from second snapshot
136
+ print("\nQuerying from second snapshot:")
137
+ updated_entry = await snapshot2.table("entries").eq("id", 1).first()
138
+ if updated_entry:
139
+ print(f"Entry 1 name in snapshot2: {updated_entry['name']}")
140
+
141
+ # Count deals in each snapshot
142
+ deals_before = await snapshot1.table("entries").eq("type", "deal").all()
143
+ deals_after = await snapshot2.table("entries").eq("type", "deal").all()
144
+ print(f"\nDeals in snapshot1: {len(deals_before)}")
145
+ print(f"Deals in snapshot2: {len(deals_after)}")
146
+
147
+ # Show new deals
148
+ print("\nNew deals added:")
149
+ for deal in deals_after:
150
+ if deal['id'] in [99001, 99002]:
151
+ print(f" - {deal['name']} (id: {deal['id']})")
152
+
153
+ finally:
154
+ # Delete the instance
155
+ print("\n\nDeleting instance...")
156
+ await env.close()
157
+ print("Instance deleted.")
158
+
159
+
160
+ if __name__ == "__main__":
161
+ asyncio.run(main())
examples/dsl_example.py CHANGED
@@ -84,6 +84,14 @@ async def main():
84
84
  print(f"Error: {response.error}")
85
85
  print(f"Message: {response.message}")
86
86
 
87
+ # Get the database resource
88
+ db = env.db()
89
+
90
+ # Take a snapshot before insertion
91
+ print("\nTaking snapshot before insertion...")
92
+ snapshot_before = await db.snapshot("before_insertion")
93
+ print(f"Snapshot created: {snapshot_before.name}")
94
+
87
95
  # Insert the deal entry
88
96
  print("\nInserting deal entry...")
89
97
  insert_query = """
@@ -103,7 +111,6 @@ async def main():
103
111
  """
104
112
 
105
113
  print("RESOURCES", await env.resources())
106
- db = env.db()
107
114
  insert_result = await db.exec(insert_query)
108
115
  print(f"Insert result: {insert_result}")
109
116
 
@@ -112,6 +119,48 @@ async def main():
112
119
  query_result = await db.query("SELECT * FROM entries WHERE id = 32302")
113
120
  print(f"Query result: {query_result}")
114
121
 
122
+ # Also verify using the new query builder
123
+ print("\nVerifying with query builder:")
124
+ entry = await db.table("entries").eq("id", 32302).first()
125
+ if entry:
126
+ print(f"Found entry: {entry['name']} (type: {entry['type']})")
127
+ # Can also use assertions
128
+ await db.table("entries").eq("id", 32302).assert_eq("type", "deal")
129
+ print("✓ Entry type assertion passed")
130
+
131
+ # Take a snapshot after insertion
132
+ print("\nTaking snapshot after insertion...")
133
+ snapshot_after = await db.snapshot("after_insertion")
134
+ print(f"Snapshot created: {snapshot_after.name}")
135
+
136
+ # Compare snapshots
137
+ print("\nComparing snapshots...")
138
+ ignore_config = IgnoreConfig(
139
+ tables={"pageviews"},
140
+ table_fields={
141
+ "entries": {"createdDate", "lastModifiedDate", "createdAt", "updatedAt"},
142
+ },
143
+ )
144
+
145
+ diff = await snapshot_before.diff(snapshot_after, ignore_config)
146
+
147
+ # Check diff results
148
+ print("\nDiff validation:")
149
+ expected_changes = [
150
+ {
151
+ "table": "entries",
152
+ "pk": 32302,
153
+ "field": None,
154
+ "after": "__added__",
155
+ }
156
+ ]
157
+
158
+ try:
159
+ await diff.expect_only(expected_changes)
160
+ print("✓ Diff validation passed - only expected changes detected")
161
+ except AssertionError as e:
162
+ print(f"✗ Diff validation failed: {e}")
163
+
115
164
  # Run verifier after insertion (should succeed)
116
165
  print("\nRunning verifier after insertion...")
117
166
  response = await env.verify(validate_new_deal_creation)
examples/example.py CHANGED
@@ -16,7 +16,7 @@ async def main():
16
16
  print("Environments:", len(environments))
17
17
 
18
18
  # Create a new instance
19
- env = await flt.env.make_async("hubspot:v1.2.7")
19
+ env = await flt.env.make_async("hubspot")
20
20
  print(f"New Instance: {env.instance_id} ({env.region})")
21
21
 
22
22
  response = await env.reset(seed=42)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ """Example demonstrating browser control with Fleet Manager Client."""
3
+
4
+ import asyncio
5
+ import fleet as flt
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+
11
+ async def main():
12
+ # Create a new instance
13
+ env = await flt.env.make_async("fira:v1.3.2")
14
+ print(f"New Instance: {env.instance_id} ({env.region})")
15
+ print("URL:", env.urls.app)
16
+
17
+ print(await env.resources())
18
+
19
+ sqlite = env.db("action_log")
20
+ print("SQLite:", await sqlite.describe())
21
+
22
+ print("Query:", await sqlite.query("SELECT * FROM action_log"))
23
+
24
+ await env.close()
25
+
26
+
27
+ if __name__ == "__main__":
28
+ asyncio.run(main())
@@ -0,0 +1,77 @@
1
+ import asyncio
2
+ import fleet as flt
3
+ from anthropic import AsyncAnthropic
4
+ from mcp import ClientSession
5
+ from mcp.client.streamable_http import streamablehttp_client
6
+ from dotenv import load_dotenv
7
+
8
+ load_dotenv()
9
+
10
+ client = AsyncAnthropic()
11
+
12
+
13
+ async def main():
14
+ env = flt.env.make("fira:v1.3.3")
15
+ print("Created environment:", env.urls.app)
16
+ print("MCP URL:", env.mcp.url)
17
+
18
+ async with streamablehttp_client(url=env.mcp.url) as streams:
19
+ async with ClientSession(read_stream=streams[0], write_stream=streams[1]) as session:
20
+ await session.initialize()
21
+
22
+ list_tools = await session.list_tools()
23
+ available_tools = [
24
+ {
25
+ "name": tool.name,
26
+ "description": tool.description,
27
+ "input_schema": tool.inputSchema,
28
+ }
29
+ for tool in list_tools.tools
30
+ ]
31
+
32
+
33
+ messages = [
34
+ {
35
+ "role": "user",
36
+ "content": "Get the current authorized user.",
37
+ },
38
+ ]
39
+ response = await client.beta.messages.create(
40
+ model="claude-sonnet-4-20250514",
41
+ max_tokens=1000,
42
+ messages=messages,
43
+ tools=available_tools,
44
+ )
45
+
46
+ tool_results = []
47
+ output_text = []
48
+ for content in response.content:
49
+ if content.type == "text":
50
+ output_text.append(content.text)
51
+ elif content.type == "tool_use":
52
+ tool_name = content.name
53
+ tool_args = content.input
54
+
55
+ result = await session.call_tool(tool_name, tool_args)
56
+ tool_results.append({"call": tool_name, "result": result})
57
+ output_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
58
+
59
+ if hasattr(content, "text") and content.text:
60
+ messages.append({"role": "assistant", "content": content.text})
61
+ messages.append({"role": "user", "content": result.content})
62
+
63
+ response = await client.messages.create(
64
+ model="claude-sonnet-4-20250514",
65
+ max_tokens=1000,
66
+ messages=messages,
67
+ )
68
+
69
+ output_text.append(response.content[0].text)
70
+
71
+ print("\n".join(output_text))
72
+
73
+ env.close()
74
+
75
+
76
+ if __name__ == "__main__":
77
+ asyncio.run(main())
@@ -0,0 +1,27 @@
1
+ import fleet as flt
2
+ from openai import OpenAI
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ client = OpenAI()
8
+
9
+
10
+ def main():
11
+ env = flt.env.make("fira:v1.3.3")
12
+ print("Created environment:", env.urls.app)
13
+ print("MCP URL:", env.mcp.url)
14
+
15
+ response = client.responses.create(
16
+ model="gpt-4.1",
17
+ tools=[env.mcp.openai()],
18
+ input="Get the current authorized user.",
19
+ )
20
+
21
+ print(response.output_text)
22
+
23
+ env.close()
24
+
25
+
26
+ if __name__ == "__main__":
27
+ main()
examples/example_sync.py CHANGED
@@ -15,7 +15,7 @@ def main():
15
15
  print("Instances:", len(instances))
16
16
 
17
17
  # Create a new instance
18
- env = flt.env.make("hubspot:v1.2.7")
18
+ env = flt.env.make("hubspot")
19
19
  print("New Instance:", env.instance_id)
20
20
 
21
21
  response = env.reset(seed=42)
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env python3
2
+ """Example demonstrating task with verifier for Jira environment.
3
+
4
+ This example shows how to create a simple task with the @verifier decorator
5
+ that can be verified in a Jira environment.
6
+
7
+ Note: The remote execution environment only supports synchronous verifiers.
8
+ For async environments, we need separate sync and async verifiers.
9
+ """
10
+
11
+ import os
12
+ import asyncio
13
+ from datetime import datetime
14
+ from fleet import AsyncFleet, verifier, TASK_SUCCESSFUL_SCORE, Task
15
+ from dotenv import load_dotenv
16
+
17
+ # Constants for task failure
18
+ TASK_FAILED_SCORE = 0.0
19
+
20
+ load_dotenv()
21
+
22
+
23
+ # For remote execution, use a synchronous verifier
24
+ # This won't work locally with async environments, but works remotely
25
+ @verifier(key="create_bug_issue_sync")
26
+ def create_bug_issue_sync(
27
+ env, project_key: str = "SCRUM", issue_title: str = "Sample Bug"
28
+ ) -> float:
29
+ """Synchronous verifier for remote execution.
30
+
31
+ Note: This is designed for remote execution where env.db() returns sync resources.
32
+ """
33
+ # Define constants locally for remote execution
34
+ TASK_SUCCESSFUL_SCORE = 1.0
35
+ TASK_FAILED_SCORE = 0.0
36
+
37
+ try:
38
+ # Get the database resource
39
+ db = env.db()
40
+
41
+ # Query for issues with the specified title and project
42
+ query = """
43
+ SELECT id, issue_type, name, project_key
44
+ FROM issues
45
+ WHERE project_key = ? AND name = ? AND issue_type = 'Bug'
46
+ """
47
+
48
+ result = db.query(query, args=[project_key, issue_title])
49
+
50
+ if result.rows and len(result.rows) > 0:
51
+ print(f"✓ Found bug issue: {result.rows[0][0]} - {result.rows[0][2]}")
52
+ return TASK_SUCCESSFUL_SCORE
53
+ else:
54
+ print(
55
+ f"✗ No bug issue found with title '{issue_title}' in project {project_key}"
56
+ )
57
+ return TASK_FAILED_SCORE
58
+
59
+ except Exception as e:
60
+ print(f"✗ Error checking for bug issue: {e}")
61
+ return TASK_FAILED_SCORE
62
+
63
+
64
+ # For local execution with async environments
65
+ @verifier(key="create_bug_issue_async")
66
+ async def create_bug_issue_async(
67
+ env, project_key: str = "SCRUM", issue_title: str = "Sample Bug"
68
+ ) -> float:
69
+ """Async verifier for local execution with async environments."""
70
+ try:
71
+ # Get the database resource
72
+ db = env.db()
73
+
74
+ # Query for issues with the specified title and project
75
+ query = """
76
+ SELECT id, issue_type, name, project_key
77
+ FROM issues
78
+ WHERE project_key = ? AND name = ? AND issue_type = 'Bug'
79
+ """
80
+
81
+ result = await db.query(query, args=[project_key, issue_title])
82
+
83
+ if result.rows and len(result.rows) > 0:
84
+ print(f"✓ Found bug issue: {result.rows[0][0]} - {result.rows[0][2]}")
85
+ return TASK_SUCCESSFUL_SCORE
86
+ else:
87
+ print(
88
+ f"✗ No bug issue found with title '{issue_title}' in project {project_key}"
89
+ )
90
+ return TASK_FAILED_SCORE
91
+
92
+ except Exception as e:
93
+ print(f"✗ Error checking for bug issue: {e}")
94
+ return TASK_FAILED_SCORE
95
+
96
+
97
+ async def main():
98
+ """Run the task example."""
99
+ print("=== Fleet Task Example with Jira ===\n")
100
+ print("Note: Remote execution only supports synchronous verifiers.\n")
101
+
102
+ # Create task using the async verifier for local execution
103
+ task = Task(
104
+ key="create-sample-bug",
105
+ prompt="Create a new bug issue titled 'Login button not working' in the SCRUM project",
106
+ env_id="fira:v1.3.1",
107
+ verifier=create_bug_issue_async,
108
+ metadata={"category": "issue_creation", "difficulty": "easy"},
109
+ )
110
+
111
+ print(f"Task definition:")
112
+ print(f" Key: {task.key}")
113
+ print(f" Prompt: {task.prompt}")
114
+ print(f" Environment: {task.env_id}")
115
+ print(f" Verifier: {task.verifier.key if hasattr(task.verifier, 'key') else 'create_bug_issue'}")
116
+ print(f" Created at: {task.created_at}")
117
+ print(f" Metadata: {task.metadata}")
118
+ print()
119
+
120
+ # Create Fleet client and environment
121
+ fleet_client = AsyncFleet()
122
+
123
+ print("Creating Jira environment...")
124
+ try:
125
+ # Create a new Jira v1.3.1 environment
126
+ env = await fleet_client.make("fira:v1.3.1")
127
+ print(f"✓ Environment created: {env.instance_id}")
128
+ print(f" URL: {env.manager_url}")
129
+ print()
130
+
131
+ # Run the async verifier locally
132
+ print("Running async verifier locally...")
133
+ result = await create_bug_issue_async(
134
+ env, project_key="SCRUM", issue_title="Login button not working"
135
+ )
136
+ print(f" Initial check result: {result}")
137
+ print()
138
+
139
+ # Test remote execution with sync verifier
140
+ print("Testing remote execution with sync verifier...")
141
+ try:
142
+ # Use the sync verifier for remote execution
143
+ remote_result = await create_bug_issue_sync.remote(
144
+ env, project_key="SCRUM", issue_title="Login button not working"
145
+ )
146
+ print(f" ✓ Remote check result: {remote_result}")
147
+ print(f" ✓ Both returned failure as expected (issue doesn't exist yet)")
148
+ except NotImplementedError as e:
149
+ print(f" ℹ️ {e}")
150
+ except Exception as e:
151
+ print(f" ✗ Remote execution failed: {e}")
152
+ print()
153
+
154
+ # Demonstrate that async verifiers can't run remotely
155
+ print("Attempting remote execution with async verifier...")
156
+ try:
157
+ await create_bug_issue_async.remote(env)
158
+ except NotImplementedError as e:
159
+ print(f" ✓ Expected error: {e}")
160
+ print()
161
+
162
+ # Create the issue
163
+ print("Creating the bug issue programmatically...")
164
+ db = env.db()
165
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
166
+
167
+ await db.exec(
168
+ """
169
+ INSERT INTO issues (id, project_key, issue_type, name, status, created_at, updated_at)
170
+ VALUES ('SCRUM-9999', 'SCRUM', 'Bug', 'Login button not working', 'Todo', ?, ?)
171
+ """,
172
+ args=[timestamp, timestamp],
173
+ )
174
+
175
+ print("✓ Bug issue created")
176
+ print()
177
+
178
+ # Run verifier again locally
179
+ print("Running async verifier locally after creating the issue...")
180
+ result = await create_bug_issue_async(
181
+ env, project_key="SCRUM", issue_title="Login button not working"
182
+ )
183
+ print(f" Final check result: {result}")
184
+ print(f" Task {'completed successfully' if result == TASK_SUCCESSFUL_SCORE else 'failed'}!")
185
+ print()
186
+
187
+ # Clean up
188
+ print("Cleaning up...")
189
+ await env.close()
190
+ print("✓ Environment closed")
191
+
192
+ except Exception as e:
193
+ print(f"✗ Error: {e}")
194
+ import traceback
195
+ traceback.print_exc()
196
+
197
+
198
+ if __name__ == "__main__":
199
+ asyncio.run(main())
@@ -0,0 +1,71 @@
1
+ import fleet as flt
2
+ from fleet.verifiers.verifier import verifier
3
+ from fleet.verifiers.db import IgnoreConfig
4
+
5
+
6
+ @verifier(key="validate_finish_blue_green_deployment")
7
+ def validate_finish_blue_green_deployment(
8
+ env: flt.Environment, final_answer: str | None = None
9
+ ) -> int:
10
+ """Validate that DEBT-722 and DEBT-720 are marked as Done"""
11
+ before = env.db("seed")
12
+ after = env.db("current")
13
+
14
+ # Check final state
15
+ try:
16
+ after.table("issues").eq("id", "DEBT-722").assert_eq("board_list", "Done")
17
+ except:
18
+ return 0
19
+ try:
20
+ after.table("issues").eq("id", "DEBT-720").assert_eq("board_list", "Done")
21
+ except:
22
+ return 0
23
+
24
+ # Configure ignore settings for this validation
25
+ ignore_config = IgnoreConfig(
26
+ tables={"activities", "pageviews"},
27
+ table_fields={
28
+ "issues": {"updated_at", "created_at", "rowid"},
29
+ "boards": {"updated_at", "created_at", "rowid"},
30
+ "projects": {"updated_at", "created_at", "rowid"},
31
+ "sprints": {"updated_at", "created_at", "rowid"},
32
+ "users": {"updated_at", "created_at", "rowid"},
33
+ },
34
+ )
35
+
36
+ # Enforce invariant: nothing else changed (with ignore configuration)
37
+ try:
38
+ before.diff(after, ignore_config).expect_only(
39
+ [
40
+ {
41
+ "table": "issues",
42
+ "pk": "DEBT-722",
43
+ "field": "board_list",
44
+ "after": "Done",
45
+ },
46
+ {
47
+ "table": "issues",
48
+ "pk": "DEBT-720",
49
+ "field": "board_list",
50
+ "after": "Done",
51
+ },
52
+ ]
53
+ )
54
+ except:
55
+ return 0
56
+
57
+ return 1
58
+
59
+
60
+ def main():
61
+ env = flt.env.make("fira:v1.3.1")
62
+ print(f"New Instance: {env.instance_id}")
63
+
64
+ print(validate_finish_blue_green_deployment(env))
65
+ print(validate_finish_blue_green_deployment.remote(env))
66
+
67
+ env.close()
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()