fleet-python 0.2.69b2__py3-none-any.whl → 0.2.70__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.
- fleet/__init__.py +3 -2
- fleet/_async/__init__.py +26 -2
- fleet/_async/base.py +21 -10
- fleet/_async/client.py +131 -201
- fleet/_async/env/client.py +38 -7
- fleet/_async/instance/client.py +4 -19
- fleet/_async/resources/sqlite.py +1 -150
- fleet/_async/tasks.py +13 -7
- fleet/_async/verifiers/bundler.py +22 -21
- fleet/_async/verifiers/verifier.py +20 -19
- fleet/base.py +21 -10
- fleet/client.py +128 -199
- fleet/config.py +1 -1
- fleet/env/__init__.py +8 -0
- fleet/env/client.py +38 -7
- fleet/instance/client.py +5 -20
- fleet/models.py +33 -0
- fleet/resources/sqlite.py +1 -143
- fleet/tasks.py +15 -7
- fleet/verifiers/bundler.py +22 -21
- fleet/verifiers/decorator.py +1 -1
- fleet/verifiers/verifier.py +20 -19
- {fleet_python-0.2.69b2.dist-info → fleet_python-0.2.70.dist-info}/METADATA +1 -1
- {fleet_python-0.2.69b2.dist-info → fleet_python-0.2.70.dist-info}/RECORD +27 -30
- tests/test_instance_dispatch.py +0 -607
- tests/test_sqlite_resource_dual_mode.py +0 -263
- tests/test_sqlite_shared_memory_behavior.py +0 -117
- {fleet_python-0.2.69b2.dist-info → fleet_python-0.2.70.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.69b2.dist-info → fleet_python-0.2.70.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.69b2.dist-info → fleet_python-0.2.70.dist-info}/top_level.txt +0 -0
tests/test_instance_dispatch.py
DELETED
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
"""Unit tests for Fleet.instance() and AsyncFleet.instance() dispatch logic."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
import tempfile
|
|
5
|
-
import sqlite3
|
|
6
|
-
import os
|
|
7
|
-
from unittest.mock import Mock, AsyncMock, patch
|
|
8
|
-
from fleet.client import Fleet
|
|
9
|
-
from fleet._async.client import AsyncFleet
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestFleetInstanceDispatch:
|
|
13
|
-
"""Test Fleet.instance() dispatching logic."""
|
|
14
|
-
|
|
15
|
-
@pytest.fixture
|
|
16
|
-
def fleet_client(self):
|
|
17
|
-
"""Create a Fleet client with mocked HTTP client."""
|
|
18
|
-
with patch("fleet.client.default_httpx_client") as mock_client:
|
|
19
|
-
mock_client.return_value = Mock()
|
|
20
|
-
client = Fleet(api_key="test_key")
|
|
21
|
-
# Mock the internal client's request method
|
|
22
|
-
client.client.request = Mock()
|
|
23
|
-
return client
|
|
24
|
-
|
|
25
|
-
@pytest.fixture
|
|
26
|
-
def temp_db_files(self):
|
|
27
|
-
"""Create temporary SQLite database files."""
|
|
28
|
-
files = {}
|
|
29
|
-
for name in ["current", "seed"]:
|
|
30
|
-
fd, path = tempfile.mkstemp(suffix=".db")
|
|
31
|
-
os.close(fd)
|
|
32
|
-
|
|
33
|
-
# Initialize with test table
|
|
34
|
-
conn = sqlite3.connect(path)
|
|
35
|
-
cursor = conn.cursor()
|
|
36
|
-
cursor.execute(f"""
|
|
37
|
-
CREATE TABLE test_{name} (
|
|
38
|
-
id INTEGER PRIMARY KEY,
|
|
39
|
-
value TEXT
|
|
40
|
-
)
|
|
41
|
-
""")
|
|
42
|
-
cursor.execute(f"INSERT INTO test_{name} (id, value) VALUES (1, '{name}_data')")
|
|
43
|
-
conn.commit()
|
|
44
|
-
conn.close()
|
|
45
|
-
|
|
46
|
-
files[name] = path
|
|
47
|
-
|
|
48
|
-
yield files
|
|
49
|
-
|
|
50
|
-
# Cleanup
|
|
51
|
-
for path in files.values():
|
|
52
|
-
if os.path.exists(path):
|
|
53
|
-
os.remove(path)
|
|
54
|
-
|
|
55
|
-
def test_dispatch_dict_local_mode(self, fleet_client, temp_db_files):
|
|
56
|
-
"""Test that dict input dispatches to local mode."""
|
|
57
|
-
env = fleet_client.instance(temp_db_files)
|
|
58
|
-
|
|
59
|
-
assert env.instance_id == "local"
|
|
60
|
-
assert env.env_key == "local"
|
|
61
|
-
assert env.region == "local"
|
|
62
|
-
|
|
63
|
-
# Verify we can access databases
|
|
64
|
-
current_db = env.db("current")
|
|
65
|
-
assert current_db is not None
|
|
66
|
-
assert current_db.mode == "direct"
|
|
67
|
-
|
|
68
|
-
seed_db = env.db("seed")
|
|
69
|
-
assert seed_db is not None
|
|
70
|
-
assert seed_db.mode == "direct"
|
|
71
|
-
|
|
72
|
-
# Verify query works
|
|
73
|
-
result = current_db.query("SELECT * FROM test_current")
|
|
74
|
-
assert result.success is True
|
|
75
|
-
assert len(result.rows) == 1
|
|
76
|
-
|
|
77
|
-
def test_dispatch_url_localhost_mode(self, fleet_client):
|
|
78
|
-
"""Test that http:// URL dispatches to localhost mode."""
|
|
79
|
-
env = fleet_client.instance("http://localhost:8080")
|
|
80
|
-
|
|
81
|
-
assert env.instance_id == "http://localhost:8080"
|
|
82
|
-
assert env.env_key == "localhost"
|
|
83
|
-
assert env.region == "localhost"
|
|
84
|
-
|
|
85
|
-
# Verify instance client is created with correct URL
|
|
86
|
-
assert env.instance.base_url == "http://localhost:8080"
|
|
87
|
-
|
|
88
|
-
def test_dispatch_https_url_localhost_mode(self, fleet_client):
|
|
89
|
-
"""Test that https:// URL dispatches to localhost mode."""
|
|
90
|
-
env = fleet_client.instance("https://custom-server.local:9000/api")
|
|
91
|
-
|
|
92
|
-
assert env.instance_id == "https://custom-server.local:9000/api"
|
|
93
|
-
assert env.env_key == "localhost"
|
|
94
|
-
|
|
95
|
-
# Verify instance client is created with correct URL
|
|
96
|
-
assert env.instance.base_url == "https://custom-server.local:9000/api"
|
|
97
|
-
|
|
98
|
-
def test_dispatch_string_remote_mode(self, fleet_client):
|
|
99
|
-
"""Test that regular string dispatches to remote mode."""
|
|
100
|
-
# Mock the HTTP response for remote mode
|
|
101
|
-
mock_response = Mock()
|
|
102
|
-
mock_response.json.return_value = {
|
|
103
|
-
"instance_id": "test-instance-123",
|
|
104
|
-
"env_key": "test_env",
|
|
105
|
-
"version": "v1.0.0",
|
|
106
|
-
"status": "running",
|
|
107
|
-
"subdomain": "test",
|
|
108
|
-
"created_at": "2025-01-01T00:00:00Z",
|
|
109
|
-
"updated_at": "2025-01-01T00:00:00Z",
|
|
110
|
-
"terminated_at": None,
|
|
111
|
-
"team_id": "team-123",
|
|
112
|
-
"region": "us-west-1",
|
|
113
|
-
"env_variables": None,
|
|
114
|
-
"data_key": None,
|
|
115
|
-
"data_version": None,
|
|
116
|
-
"urls": {
|
|
117
|
-
"root": "https://test.fleet.run/",
|
|
118
|
-
"app": ["https://test.fleet.run/app"],
|
|
119
|
-
"api": "https://test.fleet.run/api",
|
|
120
|
-
"health": "https://test.fleet.run/health",
|
|
121
|
-
"api_docs": "https://test.fleet.run/docs",
|
|
122
|
-
"manager": {
|
|
123
|
-
"api": "https://test.fleet.run/manager/api",
|
|
124
|
-
"docs": "https://test.fleet.run/manager/docs",
|
|
125
|
-
"reset": "https://test.fleet.run/manager/reset",
|
|
126
|
-
"diff": "https://test.fleet.run/manager/diff",
|
|
127
|
-
"snapshot": "https://test.fleet.run/manager/snapshot",
|
|
128
|
-
"execute_verifier_function": "https://test.fleet.run/manager/execute",
|
|
129
|
-
"execute_verifier_function_with_upload": "https://test.fleet.run/manager/execute_upload",
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
"health": True,
|
|
133
|
-
}
|
|
134
|
-
fleet_client.client.request.return_value = mock_response
|
|
135
|
-
|
|
136
|
-
env = fleet_client.instance("test-instance-123")
|
|
137
|
-
|
|
138
|
-
# Verify HTTP client was called for remote API
|
|
139
|
-
fleet_client.client.request.assert_called()
|
|
140
|
-
call_args = fleet_client.client.request.call_args
|
|
141
|
-
assert "/v1/env/instances/test-instance-123" in call_args[0][1]
|
|
142
|
-
|
|
143
|
-
# Verify we got a remote env back
|
|
144
|
-
assert env.instance_id == "test-instance-123"
|
|
145
|
-
assert env.env_key == "test_env"
|
|
146
|
-
|
|
147
|
-
def test_local_mode_query_functionality(self, fleet_client, temp_db_files):
|
|
148
|
-
"""Test that local mode databases are fully functional."""
|
|
149
|
-
env = fleet_client.instance(temp_db_files)
|
|
150
|
-
|
|
151
|
-
# Test query on current db
|
|
152
|
-
current = env.db("current")
|
|
153
|
-
result = current.query("SELECT value FROM test_current WHERE id = 1")
|
|
154
|
-
assert result.success is True
|
|
155
|
-
assert result.rows[0][0] == "current_data"
|
|
156
|
-
|
|
157
|
-
# Test query on seed db
|
|
158
|
-
seed = env.db("seed")
|
|
159
|
-
result = seed.query("SELECT value FROM test_seed WHERE id = 1")
|
|
160
|
-
assert result.success is True
|
|
161
|
-
assert result.rows[0][0] == "seed_data"
|
|
162
|
-
|
|
163
|
-
# Test query builder
|
|
164
|
-
current_data = current.table("test_current").eq("id", 1).first()
|
|
165
|
-
assert current_data is not None
|
|
166
|
-
assert current_data["value"] == "current_data"
|
|
167
|
-
|
|
168
|
-
def test_local_mode_exec_functionality(self, fleet_client, temp_db_files):
|
|
169
|
-
"""Test that local mode supports write operations."""
|
|
170
|
-
env = fleet_client.instance(temp_db_files)
|
|
171
|
-
db = env.db("current")
|
|
172
|
-
|
|
173
|
-
# Insert data
|
|
174
|
-
result = db.exec("INSERT INTO test_current (id, value) VALUES (2, 'new_data')")
|
|
175
|
-
assert result.success is True
|
|
176
|
-
assert result.rows_affected == 1
|
|
177
|
-
|
|
178
|
-
# Verify insert
|
|
179
|
-
check = db.query("SELECT value FROM test_current WHERE id = 2")
|
|
180
|
-
assert check.rows[0][0] == "new_data"
|
|
181
|
-
|
|
182
|
-
# Update data
|
|
183
|
-
result = db.exec("UPDATE test_current SET value = 'updated' WHERE id = 2")
|
|
184
|
-
assert result.success is True
|
|
185
|
-
|
|
186
|
-
# Verify update
|
|
187
|
-
check = db.query("SELECT value FROM test_current WHERE id = 2")
|
|
188
|
-
assert check.rows[0][0] == "updated"
|
|
189
|
-
|
|
190
|
-
def test_local_mode_creates_new_instances(self, fleet_client, temp_db_files):
|
|
191
|
-
"""Test that db() creates new SQLiteResource instances each time (not cached)."""
|
|
192
|
-
env = fleet_client.instance(temp_db_files)
|
|
193
|
-
|
|
194
|
-
db1 = env.db("current")
|
|
195
|
-
db2 = env.db("current")
|
|
196
|
-
|
|
197
|
-
# Should be different objects (new wrapper each time)
|
|
198
|
-
assert db1 is not db2
|
|
199
|
-
|
|
200
|
-
# But both should work identically
|
|
201
|
-
result1 = db1.query("SELECT * FROM test_current")
|
|
202
|
-
result2 = db2.query("SELECT * FROM test_current")
|
|
203
|
-
assert result1.rows == result2.rows
|
|
204
|
-
|
|
205
|
-
def test_local_mode_no_state_leakage(self, fleet_client, temp_db_files):
|
|
206
|
-
"""Test that monkey-patching one db() instance doesn't affect another."""
|
|
207
|
-
env = fleet_client.instance(temp_db_files)
|
|
208
|
-
|
|
209
|
-
db1 = env.db("current")
|
|
210
|
-
db1._custom_attribute = "test_value"
|
|
211
|
-
|
|
212
|
-
db2 = env.db("current")
|
|
213
|
-
# db2 should be a fresh instance without the custom attribute
|
|
214
|
-
assert not hasattr(db2, "_custom_attribute")
|
|
215
|
-
|
|
216
|
-
def test_memory_basic_syntax(self, fleet_client):
|
|
217
|
-
"""Test that :memory: syntax works."""
|
|
218
|
-
env = fleet_client.instance({
|
|
219
|
-
"current": ":memory:"
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
db = env.db("current")
|
|
223
|
-
assert db.mode == "direct"
|
|
224
|
-
|
|
225
|
-
# Create table and insert data
|
|
226
|
-
result = db.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
227
|
-
assert result.success is True
|
|
228
|
-
|
|
229
|
-
result = db.exec("INSERT INTO test VALUES (1, 'hello')")
|
|
230
|
-
assert result.success is True
|
|
231
|
-
|
|
232
|
-
# Verify data exists
|
|
233
|
-
result = db.query("SELECT * FROM test")
|
|
234
|
-
assert result.success is True
|
|
235
|
-
assert result.rows == [[1, 'hello']]
|
|
236
|
-
|
|
237
|
-
def test_memory_namespace_syntax(self, fleet_client):
|
|
238
|
-
"""Test that :memory:namespace syntax creates isolated databases."""
|
|
239
|
-
env = fleet_client.instance({
|
|
240
|
-
"current": ":memory:current",
|
|
241
|
-
"seed": ":memory:seed"
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
current = env.db("current")
|
|
245
|
-
seed = env.db("seed")
|
|
246
|
-
|
|
247
|
-
# Create table in current
|
|
248
|
-
current.exec("CREATE TABLE test_current (id INTEGER, value TEXT)")
|
|
249
|
-
current.exec("INSERT INTO test_current VALUES (1, 'current_data')")
|
|
250
|
-
|
|
251
|
-
# Create table in seed
|
|
252
|
-
seed.exec("CREATE TABLE test_seed (id INTEGER, value TEXT)")
|
|
253
|
-
seed.exec("INSERT INTO test_seed VALUES (2, 'seed_data')")
|
|
254
|
-
|
|
255
|
-
# Verify current has its data
|
|
256
|
-
result = current.query("SELECT * FROM test_current")
|
|
257
|
-
assert result.success is True
|
|
258
|
-
assert result.rows == [[1, 'current_data']]
|
|
259
|
-
|
|
260
|
-
# Verify seed has its data
|
|
261
|
-
result = seed.query("SELECT * FROM test_seed")
|
|
262
|
-
assert result.success is True
|
|
263
|
-
assert result.rows == [[2, 'seed_data']]
|
|
264
|
-
|
|
265
|
-
# Verify current doesn't have seed's table
|
|
266
|
-
result = current.query("SELECT name FROM sqlite_master WHERE type='table' AND name='test_seed'")
|
|
267
|
-
assert result.success is True
|
|
268
|
-
assert result.rows is None or len(result.rows) == 0
|
|
269
|
-
|
|
270
|
-
# Verify seed doesn't have current's table
|
|
271
|
-
result = seed.query("SELECT name FROM sqlite_master WHERE type='table' AND name='test_current'")
|
|
272
|
-
assert result.success is True
|
|
273
|
-
assert result.rows is None or len(result.rows) == 0
|
|
274
|
-
|
|
275
|
-
def test_memory_data_sharing(self, fleet_client):
|
|
276
|
-
"""Test that multiple db() calls to same namespace share data."""
|
|
277
|
-
env = fleet_client.instance({
|
|
278
|
-
"current": ":memory:shared"
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
# Create table and insert data via first connection
|
|
282
|
-
db1 = env.db("current")
|
|
283
|
-
db1.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
284
|
-
db1.exec("INSERT INTO test VALUES (1, 'shared_data')")
|
|
285
|
-
|
|
286
|
-
# Access same database via second connection
|
|
287
|
-
db2 = env.db("current")
|
|
288
|
-
result = db2.query("SELECT * FROM test")
|
|
289
|
-
assert result.success is True
|
|
290
|
-
assert result.rows == [[1, 'shared_data']]
|
|
291
|
-
|
|
292
|
-
# Insert via second connection
|
|
293
|
-
db2.exec("INSERT INTO test VALUES (2, 'more_data')")
|
|
294
|
-
|
|
295
|
-
# Verify via first connection
|
|
296
|
-
result = db1.query("SELECT * FROM test ORDER BY id")
|
|
297
|
-
assert result.success is True
|
|
298
|
-
assert result.rows == [[1, 'shared_data'], [2, 'more_data']]
|
|
299
|
-
|
|
300
|
-
def test_memory_data_persists(self, fleet_client):
|
|
301
|
-
"""Test that memory data persists while env is alive."""
|
|
302
|
-
env = fleet_client.instance({
|
|
303
|
-
"current": ":memory:persistent"
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
# Create and populate database
|
|
307
|
-
db = env.db("current")
|
|
308
|
-
db.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
309
|
-
db.exec("INSERT INTO test VALUES (1, 'persistent')")
|
|
310
|
-
|
|
311
|
-
# Delete the db reference
|
|
312
|
-
del db
|
|
313
|
-
|
|
314
|
-
# Create new connection - data should still be there
|
|
315
|
-
db_new = env.db("current")
|
|
316
|
-
result = db_new.query("SELECT * FROM test")
|
|
317
|
-
assert result.success is True
|
|
318
|
-
assert result.rows == [[1, 'persistent']]
|
|
319
|
-
|
|
320
|
-
def test_memory_anchor_connection_cleanup(self, fleet_client):
|
|
321
|
-
"""Test that closing the instance cleans up anchor connections."""
|
|
322
|
-
env = fleet_client.instance({
|
|
323
|
-
"current": ":memory:cleanup_test",
|
|
324
|
-
"seed": ":memory:cleanup_test2"
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
# Verify anchor connections were created
|
|
328
|
-
assert hasattr(env._instance, '_memory_anchors')
|
|
329
|
-
assert 'current' in env._instance._memory_anchors
|
|
330
|
-
assert 'seed' in env._instance._memory_anchors
|
|
331
|
-
|
|
332
|
-
# Setup databases
|
|
333
|
-
current = env.db("current")
|
|
334
|
-
current.exec("CREATE TABLE test (id INTEGER)")
|
|
335
|
-
|
|
336
|
-
# Close the instance
|
|
337
|
-
env._instance.close()
|
|
338
|
-
|
|
339
|
-
# Verify anchors were cleaned up
|
|
340
|
-
assert len(env._instance._memory_anchors) == 0
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
@pytest.mark.asyncio
|
|
344
|
-
class TestAsyncFleetInstanceDispatch:
|
|
345
|
-
"""Test AsyncFleet.instance() dispatching logic."""
|
|
346
|
-
|
|
347
|
-
@pytest.fixture
|
|
348
|
-
async def async_fleet_client(self):
|
|
349
|
-
"""Create an AsyncFleet client with mocked HTTP client."""
|
|
350
|
-
with patch("fleet._async.client.default_httpx_client") as mock_client:
|
|
351
|
-
mock_client.return_value = AsyncMock()
|
|
352
|
-
client = AsyncFleet(api_key="test_key")
|
|
353
|
-
# Mock the internal client's request method
|
|
354
|
-
client.client.request = AsyncMock()
|
|
355
|
-
return client
|
|
356
|
-
|
|
357
|
-
@pytest.fixture
|
|
358
|
-
def temp_db_files(self):
|
|
359
|
-
"""Create temporary SQLite database files."""
|
|
360
|
-
files = {}
|
|
361
|
-
for name in ["current", "seed"]:
|
|
362
|
-
fd, path = tempfile.mkstemp(suffix=".db")
|
|
363
|
-
os.close(fd)
|
|
364
|
-
|
|
365
|
-
# Initialize with test table
|
|
366
|
-
conn = sqlite3.connect(path)
|
|
367
|
-
cursor = conn.cursor()
|
|
368
|
-
cursor.execute(f"""
|
|
369
|
-
CREATE TABLE test_{name} (
|
|
370
|
-
id INTEGER PRIMARY KEY,
|
|
371
|
-
value TEXT
|
|
372
|
-
)
|
|
373
|
-
""")
|
|
374
|
-
cursor.execute(f"INSERT INTO test_{name} (id, value) VALUES (1, '{name}_data')")
|
|
375
|
-
conn.commit()
|
|
376
|
-
conn.close()
|
|
377
|
-
|
|
378
|
-
files[name] = path
|
|
379
|
-
|
|
380
|
-
yield files
|
|
381
|
-
|
|
382
|
-
# Cleanup
|
|
383
|
-
for path in files.values():
|
|
384
|
-
if os.path.exists(path):
|
|
385
|
-
os.remove(path)
|
|
386
|
-
|
|
387
|
-
async def test_dispatch_dict_local_mode(self, async_fleet_client, temp_db_files):
|
|
388
|
-
"""Test that dict input dispatches to local mode in async."""
|
|
389
|
-
env = await async_fleet_client.instance(temp_db_files)
|
|
390
|
-
|
|
391
|
-
assert env.instance_id == "local"
|
|
392
|
-
assert env.env_key == "local"
|
|
393
|
-
assert env.region == "local"
|
|
394
|
-
|
|
395
|
-
# Verify we can access databases
|
|
396
|
-
current_db = env.db("current")
|
|
397
|
-
assert current_db is not None
|
|
398
|
-
assert current_db.mode == "direct"
|
|
399
|
-
|
|
400
|
-
# Verify async query works
|
|
401
|
-
result = await current_db.query("SELECT * FROM test_current")
|
|
402
|
-
assert result.success is True
|
|
403
|
-
assert len(result.rows) == 1
|
|
404
|
-
|
|
405
|
-
async def test_dispatch_url_localhost_mode(self, async_fleet_client):
|
|
406
|
-
"""Test that http:// URL dispatches to localhost mode in async."""
|
|
407
|
-
env = await async_fleet_client.instance("http://localhost:8080")
|
|
408
|
-
|
|
409
|
-
assert env.instance_id == "http://localhost:8080"
|
|
410
|
-
assert env.env_key == "localhost"
|
|
411
|
-
|
|
412
|
-
# Verify instance client is created with correct URL
|
|
413
|
-
assert env.instance.base_url == "http://localhost:8080"
|
|
414
|
-
|
|
415
|
-
async def test_local_mode_async_query_functionality(self, async_fleet_client, temp_db_files):
|
|
416
|
-
"""Test that local mode databases work with async queries."""
|
|
417
|
-
env = await async_fleet_client.instance(temp_db_files)
|
|
418
|
-
|
|
419
|
-
# Test async query
|
|
420
|
-
current = env.db("current")
|
|
421
|
-
result = await current.query("SELECT value FROM test_current WHERE id = 1")
|
|
422
|
-
assert result.success is True
|
|
423
|
-
assert result.rows[0][0] == "current_data"
|
|
424
|
-
|
|
425
|
-
# Test async query builder
|
|
426
|
-
current_data = await current.table("test_current").eq("id", 1).first()
|
|
427
|
-
assert current_data is not None
|
|
428
|
-
assert current_data["value"] == "current_data"
|
|
429
|
-
|
|
430
|
-
async def test_local_mode_async_exec_functionality(self, async_fleet_client, temp_db_files):
|
|
431
|
-
"""Test that local mode supports async write operations."""
|
|
432
|
-
env = await async_fleet_client.instance(temp_db_files)
|
|
433
|
-
db = env.db("current")
|
|
434
|
-
|
|
435
|
-
# Insert data
|
|
436
|
-
result = await db.exec("INSERT INTO test_current (id, value) VALUES (2, 'async_data')")
|
|
437
|
-
assert result.success is True
|
|
438
|
-
assert result.rows_affected == 1
|
|
439
|
-
|
|
440
|
-
# Verify insert
|
|
441
|
-
check = await db.query("SELECT value FROM test_current WHERE id = 2")
|
|
442
|
-
assert check.rows[0][0] == "async_data"
|
|
443
|
-
|
|
444
|
-
async def test_local_mode_async_creates_new_instances(self, async_fleet_client, temp_db_files):
|
|
445
|
-
"""Test that db() creates new AsyncSQLiteResource instances each time (not cached)."""
|
|
446
|
-
env = await async_fleet_client.instance(temp_db_files)
|
|
447
|
-
|
|
448
|
-
db1 = env.db("current")
|
|
449
|
-
db2 = env.db("current")
|
|
450
|
-
|
|
451
|
-
# Should be different objects (new wrapper each time)
|
|
452
|
-
assert db1 is not db2
|
|
453
|
-
|
|
454
|
-
# But both should work identically
|
|
455
|
-
result1 = await db1.query("SELECT * FROM test_current")
|
|
456
|
-
result2 = await db2.query("SELECT * FROM test_current")
|
|
457
|
-
assert result1.rows == result2.rows
|
|
458
|
-
|
|
459
|
-
async def test_local_mode_async_no_state_leakage(self, async_fleet_client, temp_db_files):
|
|
460
|
-
"""Test that monkey-patching one db() instance doesn't affect another in async."""
|
|
461
|
-
env = await async_fleet_client.instance(temp_db_files)
|
|
462
|
-
|
|
463
|
-
db1 = env.db("current")
|
|
464
|
-
db1._custom_attribute = "test_value"
|
|
465
|
-
|
|
466
|
-
db2 = env.db("current")
|
|
467
|
-
# db2 should be a fresh instance without the custom attribute
|
|
468
|
-
assert not hasattr(db2, "_custom_attribute")
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
@pytest.mark.asyncio
|
|
472
|
-
class TestAsyncFleetInMemoryTests:
|
|
473
|
-
"""Test AsyncFleet in-memory functionality."""
|
|
474
|
-
|
|
475
|
-
@pytest.fixture
|
|
476
|
-
async def async_fleet_client(self):
|
|
477
|
-
"""Create an AsyncFleet client with mocked HTTP client."""
|
|
478
|
-
with patch("fleet._async.client.default_httpx_client") as mock_client:
|
|
479
|
-
mock_client.return_value = AsyncMock()
|
|
480
|
-
client = AsyncFleet(api_key="test_key")
|
|
481
|
-
client.client.request = AsyncMock()
|
|
482
|
-
return client
|
|
483
|
-
|
|
484
|
-
async def test_memory_basic_syntax(self, async_fleet_client):
|
|
485
|
-
"""Test that :memory: syntax works in async."""
|
|
486
|
-
env = await async_fleet_client.instance({
|
|
487
|
-
"current": ":memory:"
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
db = env.db("current")
|
|
491
|
-
assert db.mode == "direct"
|
|
492
|
-
|
|
493
|
-
# Create table and insert data
|
|
494
|
-
result = await db.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
495
|
-
assert result.success is True
|
|
496
|
-
|
|
497
|
-
result = await db.exec("INSERT INTO test VALUES (1, 'hello')")
|
|
498
|
-
assert result.success is True
|
|
499
|
-
|
|
500
|
-
# Verify data exists
|
|
501
|
-
result = await db.query("SELECT * FROM test")
|
|
502
|
-
assert result.success is True
|
|
503
|
-
assert result.rows == [[1, 'hello']]
|
|
504
|
-
|
|
505
|
-
async def test_memory_namespace_syntax(self, async_fleet_client):
|
|
506
|
-
"""Test that :memory:namespace syntax creates isolated databases in async."""
|
|
507
|
-
env = await async_fleet_client.instance({
|
|
508
|
-
"current": ":memory:current",
|
|
509
|
-
"seed": ":memory:seed"
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
current = env.db("current")
|
|
513
|
-
seed = env.db("seed")
|
|
514
|
-
|
|
515
|
-
# Create table in current
|
|
516
|
-
await current.exec("CREATE TABLE test_current (id INTEGER, value TEXT)")
|
|
517
|
-
await current.exec("INSERT INTO test_current VALUES (1, 'current_data')")
|
|
518
|
-
|
|
519
|
-
# Create table in seed
|
|
520
|
-
await seed.exec("CREATE TABLE test_seed (id INTEGER, value TEXT)")
|
|
521
|
-
await seed.exec("INSERT INTO test_seed VALUES (2, 'seed_data')")
|
|
522
|
-
|
|
523
|
-
# Verify current has its data
|
|
524
|
-
result = await current.query("SELECT * FROM test_current")
|
|
525
|
-
assert result.success is True
|
|
526
|
-
assert result.rows == [[1, 'current_data']]
|
|
527
|
-
|
|
528
|
-
# Verify seed has its data
|
|
529
|
-
result = await seed.query("SELECT * FROM test_seed")
|
|
530
|
-
assert result.success is True
|
|
531
|
-
assert result.rows == [[2, 'seed_data']]
|
|
532
|
-
|
|
533
|
-
# Verify namespaces are isolated
|
|
534
|
-
result = await current.query("SELECT name FROM sqlite_master WHERE type='table' AND name='test_seed'")
|
|
535
|
-
assert result.success is True
|
|
536
|
-
assert result.rows is None or len(result.rows) == 0
|
|
537
|
-
|
|
538
|
-
async def test_memory_data_sharing(self, async_fleet_client):
|
|
539
|
-
"""Test that multiple db() calls to same namespace share data in async."""
|
|
540
|
-
env = await async_fleet_client.instance({
|
|
541
|
-
"current": ":memory:shared"
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
# Create table and insert data via first connection
|
|
545
|
-
db1 = env.db("current")
|
|
546
|
-
await db1.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
547
|
-
await db1.exec("INSERT INTO test VALUES (1, 'shared_data')")
|
|
548
|
-
|
|
549
|
-
# Access same database via second connection
|
|
550
|
-
db2 = env.db("current")
|
|
551
|
-
result = await db2.query("SELECT * FROM test")
|
|
552
|
-
assert result.success is True
|
|
553
|
-
assert result.rows == [[1, 'shared_data']]
|
|
554
|
-
|
|
555
|
-
# Insert via second connection
|
|
556
|
-
await db2.exec("INSERT INTO test VALUES (2, 'more_data')")
|
|
557
|
-
|
|
558
|
-
# Verify via first connection
|
|
559
|
-
result = await db1.query("SELECT * FROM test ORDER BY id")
|
|
560
|
-
assert result.success is True
|
|
561
|
-
assert result.rows == [[1, 'shared_data'], [2, 'more_data']]
|
|
562
|
-
|
|
563
|
-
async def test_memory_data_persists(self, async_fleet_client):
|
|
564
|
-
"""Test that memory data persists while env is alive in async."""
|
|
565
|
-
env = await async_fleet_client.instance({
|
|
566
|
-
"current": ":memory:persistent"
|
|
567
|
-
})
|
|
568
|
-
|
|
569
|
-
# Create and populate database
|
|
570
|
-
db = env.db("current")
|
|
571
|
-
await db.exec("CREATE TABLE test (id INTEGER, value TEXT)")
|
|
572
|
-
await db.exec("INSERT INTO test VALUES (1, 'persistent')")
|
|
573
|
-
|
|
574
|
-
# Delete the db reference
|
|
575
|
-
del db
|
|
576
|
-
|
|
577
|
-
# Create new connection - data should still be there
|
|
578
|
-
db_new = env.db("current")
|
|
579
|
-
result = await db_new.query("SELECT * FROM test")
|
|
580
|
-
assert result.success is True
|
|
581
|
-
assert result.rows == [[1, 'persistent']]
|
|
582
|
-
|
|
583
|
-
async def test_memory_anchor_connection_cleanup(self, async_fleet_client):
|
|
584
|
-
"""Test that closing the instance cleans up anchor connections in async."""
|
|
585
|
-
env = await async_fleet_client.instance({
|
|
586
|
-
"current": ":memory:cleanup_test",
|
|
587
|
-
"seed": ":memory:cleanup_test2"
|
|
588
|
-
})
|
|
589
|
-
|
|
590
|
-
# Verify anchor connections were created
|
|
591
|
-
assert hasattr(env._instance, '_memory_anchors')
|
|
592
|
-
assert 'current' in env._instance._memory_anchors
|
|
593
|
-
assert 'seed' in env._instance._memory_anchors
|
|
594
|
-
|
|
595
|
-
# Setup databases
|
|
596
|
-
current = env.db("current")
|
|
597
|
-
await current.exec("CREATE TABLE test (id INTEGER)")
|
|
598
|
-
|
|
599
|
-
# Close the instance
|
|
600
|
-
env._instance.close()
|
|
601
|
-
|
|
602
|
-
# Verify anchors were cleaned up
|
|
603
|
-
assert len(env._instance._memory_anchors) == 0
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if __name__ == "__main__":
|
|
607
|
-
pytest.main([__file__, "-v"])
|