fleet-python 0.2.13__tar.gz → 0.2.16__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.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- {fleet_python-0.2.13 → fleet_python-0.2.16}/PKG-INFO +3 -42
- {fleet_python-0.2.13 → fleet_python-0.2.16}/README.md +0 -41
- fleet_python-0.2.16/examples/diff_example.py +161 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/dsl_example.py +50 -1
- fleet_python-0.2.16/examples/example_action_log.py +28 -0
- fleet_python-0.2.16/examples/example_mcp_anthropic.py +77 -0
- fleet_python-0.2.16/examples/example_mcp_openai.py +27 -0
- fleet_python-0.2.16/examples/example_task.py +199 -0
- fleet_python-0.2.16/examples/example_verifier.py +71 -0
- fleet_python-0.2.16/examples/query_builder_example.py +117 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/__init__.py +51 -40
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/base.py +14 -1
- fleet_python-0.2.16/fleet/_async/client.py +298 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/env/client.py +4 -4
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/instance/__init__.py +1 -2
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/instance/client.py +3 -2
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/playwright.py +2 -2
- fleet_python-0.2.16/fleet/_async/resources/sqlite.py +695 -0
- fleet_python-0.2.16/fleet/_async/tasks.py +44 -0
- fleet_python-0.2.16/fleet/_async/verifiers/__init__.py +17 -0
- fleet_python-0.2.16/fleet/_async/verifiers/bundler.py +699 -0
- fleet_python-0.2.16/fleet/_async/verifiers/verifier.py +301 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/base.py +14 -1
- fleet_python-0.2.16/fleet/client.py +814 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/config.py +1 -1
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/instance/__init__.py +1 -2
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/instance/client.py +15 -5
- fleet_python-0.2.16/fleet/models.py +276 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/resources/browser.py +7 -8
- fleet_python-0.2.16/fleet/resources/mcp.py +60 -0
- fleet_python-0.2.16/fleet/resources/sqlite.py +695 -0
- fleet_python-0.2.16/fleet/tasks.py +44 -0
- fleet_python-0.2.16/fleet/types.py +18 -0
- fleet_python-0.2.16/fleet/verifiers/__init__.py +17 -0
- fleet_python-0.2.16/fleet/verifiers/bundler.py +699 -0
- fleet_python-0.2.16/fleet/verifiers/decorator.py +103 -0
- fleet_python-0.2.16/fleet/verifiers/verifier.py +301 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet_python.egg-info/PKG-INFO +3 -42
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet_python.egg-info/SOURCES.txt +17 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet_python.egg-info/requires.txt +2 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/pyproject.toml +5 -2
- fleet_python-0.2.13/fleet/_async/client.py +0 -162
- fleet_python-0.2.13/fleet/_async/resources/sqlite.py +0 -41
- fleet_python-0.2.13/fleet/client.py +0 -162
- fleet_python-0.2.13/fleet/models.py +0 -109
- fleet_python-0.2.13/fleet/resources/sqlite.py +0 -41
- fleet_python-0.2.13/fleet/verifiers/__init__.py +0 -11
- {fleet_python-0.2.13 → fleet_python-0.2.16}/LICENSE +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/example_client.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/example_sync.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/gemini_example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/json_tasks_example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/nova_act_example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/openai_example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/openai_simple_example.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/examples/quickstart.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/__init__.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/env/__init__.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/exceptions.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/instance/base.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/resources/__init__.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/resources/base.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/_async/resources/browser.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/env/__init__.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/env/client.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/exceptions.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/instance/base.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/instance/models.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/playwright.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/resources/__init__.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/resources/base.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/verifiers/code.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/verifiers/db.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet/verifiers/sql_differ.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet_python.egg-info/dependency_links.txt +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/fleet_python.egg-info/top_level.txt +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/scripts/fix_sync_imports.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/scripts/unasync.py +0 -0
- {fleet_python-0.2.13 → fleet_python-0.2.16}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fleet-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Python SDK for Fleet environments
|
|
5
5
|
Author-email: Fleet AI <nic@fleet.so>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -25,6 +25,8 @@ Requires-Dist: pydantic>=2.0.0
|
|
|
25
25
|
Requires-Dist: httpx>=0.27.0
|
|
26
26
|
Requires-Dist: httpx-retries>=0.4.0
|
|
27
27
|
Requires-Dist: typing-extensions>=4.0.0
|
|
28
|
+
Requires-Dist: modulegraph2>=0.2.0
|
|
29
|
+
Requires-Dist: cloudpickle==3.1.1
|
|
28
30
|
Provides-Extra: dev
|
|
29
31
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
32
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -114,44 +116,3 @@ running_instances = flt.env.list_instances(status_filter="running")
|
|
|
114
116
|
# List available environment types
|
|
115
117
|
available_envs = flt.env.list_envs()
|
|
116
118
|
```
|
|
117
|
-
|
|
118
|
-
## Development
|
|
119
|
-
|
|
120
|
-
### Code Structure
|
|
121
|
-
|
|
122
|
-
This SDK uses `unasync` to maintain both async and sync versions of the code from a single source:
|
|
123
|
-
|
|
124
|
-
- **`fleet/_async/`** - The source code (async version) - **EDIT THIS**
|
|
125
|
-
- **`fleet/`** - The generated sync version - **DO NOT EDIT** (auto-generated)
|
|
126
|
-
|
|
127
|
-
### Making Changes
|
|
128
|
-
|
|
129
|
-
⚠️ **Important**: All code modifications should be made in the `fleet/_async/` directory. The synchronous versions in `fleet/` are automatically generated.
|
|
130
|
-
|
|
131
|
-
1. Make your changes in `fleet/_async/`
|
|
132
|
-
2. Run `make unasync` to generate the sync versions
|
|
133
|
-
3. Test both async and sync versions
|
|
134
|
-
4. Commit all changes (both async source and generated sync files)
|
|
135
|
-
|
|
136
|
-
Example workflow:
|
|
137
|
-
```bash
|
|
138
|
-
# Edit the async source files
|
|
139
|
-
vim fleet/_async/client.py
|
|
140
|
-
|
|
141
|
-
# Generate sync versions
|
|
142
|
-
make unasync
|
|
143
|
-
|
|
144
|
-
# Run tests
|
|
145
|
-
python examples/examle.py
|
|
146
|
-
|
|
147
|
-
# Commit both source and generated files
|
|
148
|
-
git add fleet/_async/ fleet/
|
|
149
|
-
git commit -m "Add new feature"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Why This Structure?
|
|
153
|
-
|
|
154
|
-
- Single source of truth for business logic
|
|
155
|
-
- Automatic sync/async API generation
|
|
156
|
-
- Consistent behavior between sync and async versions
|
|
157
|
-
- Easier maintenance and testing
|
|
@@ -74,44 +74,3 @@ running_instances = flt.env.list_instances(status_filter="running")
|
|
|
74
74
|
# List available environment types
|
|
75
75
|
available_envs = flt.env.list_envs()
|
|
76
76
|
```
|
|
77
|
-
|
|
78
|
-
## Development
|
|
79
|
-
|
|
80
|
-
### Code Structure
|
|
81
|
-
|
|
82
|
-
This SDK uses `unasync` to maintain both async and sync versions of the code from a single source:
|
|
83
|
-
|
|
84
|
-
- **`fleet/_async/`** - The source code (async version) - **EDIT THIS**
|
|
85
|
-
- **`fleet/`** - The generated sync version - **DO NOT EDIT** (auto-generated)
|
|
86
|
-
|
|
87
|
-
### Making Changes
|
|
88
|
-
|
|
89
|
-
⚠️ **Important**: All code modifications should be made in the `fleet/_async/` directory. The synchronous versions in `fleet/` are automatically generated.
|
|
90
|
-
|
|
91
|
-
1. Make your changes in `fleet/_async/`
|
|
92
|
-
2. Run `make unasync` to generate the sync versions
|
|
93
|
-
3. Test both async and sync versions
|
|
94
|
-
4. Commit all changes (both async source and generated sync files)
|
|
95
|
-
|
|
96
|
-
Example workflow:
|
|
97
|
-
```bash
|
|
98
|
-
# Edit the async source files
|
|
99
|
-
vim fleet/_async/client.py
|
|
100
|
-
|
|
101
|
-
# Generate sync versions
|
|
102
|
-
make unasync
|
|
103
|
-
|
|
104
|
-
# Run tests
|
|
105
|
-
python examples/examle.py
|
|
106
|
-
|
|
107
|
-
# Commit both source and generated files
|
|
108
|
-
git add fleet/_async/ fleet/
|
|
109
|
-
git commit -m "Add new feature"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Why This Structure?
|
|
113
|
-
|
|
114
|
-
- Single source of truth for business logic
|
|
115
|
-
- Automatic sync/async API generation
|
|
116
|
-
- Consistent behavior between sync and async versions
|
|
117
|
-
- Easier maintenance and testing
|
|
@@ -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())
|
|
@@ -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)
|
|
@@ -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()
|
|
@@ -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())
|