fleet-python 0.2.2__py3-none-any.whl → 0.2.3__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.
- examples/dsl_example.py +107 -92
- examples/json_tasks_example.py +82 -0
- examples/nova_act_example.py +18 -169
- examples/openai_example.py +83 -298
- examples/openai_simple_example.py +61 -0
- examples/quickstart.py +5 -5
- fleet/__init__.py +15 -1
- fleet/client.py +18 -3
- fleet/{manager → instance}/__init__.py +4 -1
- fleet/{manager → instance}/client.py +42 -5
- fleet/{manager → instance}/models.py +13 -0
- fleet/playwright.py +291 -0
- fleet/resources/base.py +1 -1
- fleet/resources/browser.py +6 -9
- fleet/resources/sqlite.py +3 -3
- fleet/verifiers/__init__.py +15 -3
- fleet/verifiers/code.py +132 -0
- fleet/verifiers/{database_snapshot.py → db.py} +62 -22
- fleet/verifiers/sql_differ.py +1 -1
- {fleet_python-0.2.2.dist-info → fleet_python-0.2.3.dist-info}/METADATA +3 -1
- fleet_python-0.2.3.dist-info/RECORD +31 -0
- fleet_python-0.2.2.dist-info/RECORD +0 -27
- /fleet/{manager → instance}/base.py +0 -0
- {fleet_python-0.2.2.dist-info → fleet_python-0.2.3.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.2.dist-info → fleet_python-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.2.dist-info → fleet_python-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# database_dsl.py
|
|
2
1
|
"""A schema‑agnostic, SQL‑native DSL for snapshot validation and diff invariants.
|
|
3
2
|
|
|
4
3
|
The module extends your original `DatabaseSnapshot` implementation with
|
|
@@ -11,11 +10,13 @@ The module extends your original `DatabaseSnapshot` implementation with
|
|
|
11
10
|
The public API stays tiny yet composable; everything else is built on
|
|
12
11
|
orthogonal primitives so it works for *any* relational schema.
|
|
13
12
|
"""
|
|
13
|
+
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import sqlite3
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from typing import Any
|
|
19
|
+
import json
|
|
19
20
|
|
|
20
21
|
################################################################################
|
|
21
22
|
# Low‑level helpers
|
|
@@ -26,6 +27,36 @@ Condition = tuple[str, str, SQLValue] # (column, op, value)
|
|
|
26
27
|
JoinSpec = tuple[str, dict[str, str]] # (table, on mapping)
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def _is_json_string(value: Any) -> bool:
|
|
31
|
+
"""Check if a value looks like a JSON string."""
|
|
32
|
+
if not isinstance(value, str):
|
|
33
|
+
return False
|
|
34
|
+
value = value.strip()
|
|
35
|
+
return (value.startswith("{") and value.endswith("}")) or (
|
|
36
|
+
value.startswith("[") and value.endswith("]")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _values_equivalent(val1: Any, val2: Any) -> bool:
|
|
41
|
+
"""Compare two values, using JSON semantic comparison for JSON strings."""
|
|
42
|
+
# If both are exactly equal, return True
|
|
43
|
+
if val1 == val2:
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
# If both look like JSON strings, try semantic comparison
|
|
47
|
+
if _is_json_string(val1) and _is_json_string(val2):
|
|
48
|
+
try:
|
|
49
|
+
parsed1 = json.loads(val1)
|
|
50
|
+
parsed2 = json.loads(val2)
|
|
51
|
+
return parsed1 == parsed2
|
|
52
|
+
except (json.JSONDecodeError, TypeError):
|
|
53
|
+
# If parsing fails, fall back to string comparison
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
# Default to exact comparison
|
|
57
|
+
return val1 == val2
|
|
58
|
+
|
|
59
|
+
|
|
29
60
|
class _CountResult:
|
|
30
61
|
"""Wraps an integer count so we can chain assertions fluently."""
|
|
31
62
|
|
|
@@ -96,9 +127,7 @@ class QueryBuilder:
|
|
|
96
127
|
# ---------------------------------------------------------------------
|
|
97
128
|
# WHERE helpers (SQL‑like)
|
|
98
129
|
# ---------------------------------------------------------------------
|
|
99
|
-
def _add_condition(
|
|
100
|
-
self, column: str, op: str, value: SQLValue
|
|
101
|
-
) -> "QueryBuilder": # noqa: UP037
|
|
130
|
+
def _add_condition(self, column: str, op: str, value: SQLValue) -> "QueryBuilder": # noqa: UP037
|
|
102
131
|
qb = self._clone()
|
|
103
132
|
qb._conditions.append((column, op, value))
|
|
104
133
|
return qb
|
|
@@ -126,9 +155,7 @@ class QueryBuilder:
|
|
|
126
155
|
qb._conditions.append((column, "IN", tuple(values)))
|
|
127
156
|
return qb
|
|
128
157
|
|
|
129
|
-
def not_in(
|
|
130
|
-
self, column: str, values: list[SQLValue]
|
|
131
|
-
) -> "QueryBuilder": # noqa: UP037
|
|
158
|
+
def not_in(self, column: str, values: list[SQLValue]) -> "QueryBuilder": # noqa: UP037
|
|
132
159
|
qb = self._clone()
|
|
133
160
|
qb._conditions.append((column, "NOT IN", tuple(values)))
|
|
134
161
|
return qb
|
|
@@ -147,9 +174,7 @@ class QueryBuilder:
|
|
|
147
174
|
# ---------------------------------------------------------------------
|
|
148
175
|
# JOIN (simple inner join)
|
|
149
176
|
# ---------------------------------------------------------------------
|
|
150
|
-
def join(
|
|
151
|
-
self, other_table: str, on: dict[str, str]
|
|
152
|
-
) -> "QueryBuilder": # noqa: UP037
|
|
177
|
+
def join(self, other_table: str, on: dict[str, str]) -> "QueryBuilder": # noqa: UP037
|
|
153
178
|
"""`on` expects {local_col: remote_col}."""
|
|
154
179
|
qb = self._clone()
|
|
155
180
|
qb._joins.append((other_table, on))
|
|
@@ -166,7 +191,8 @@ class QueryBuilder:
|
|
|
166
191
|
# Joins -------------------------------------------------------------
|
|
167
192
|
for tbl, onmap in self._joins:
|
|
168
193
|
join_clauses = [
|
|
169
|
-
f"{self._table}.{l} = {tbl}.{r}"
|
|
194
|
+
f"{self._table}.{l} = {tbl}.{r}"
|
|
195
|
+
for l, r in onmap.items() # noqa: E741
|
|
170
196
|
]
|
|
171
197
|
sql.append(f"JOIN {tbl} ON {' AND '.join(join_clauses)}")
|
|
172
198
|
|
|
@@ -430,10 +456,27 @@ class SnapshotDiff:
|
|
|
430
456
|
def expect_only(self, allowed_changes: list[dict[str, Any]]):
|
|
431
457
|
"""Allowed changes is a list of {table, pk, field, after} (before optional)."""
|
|
432
458
|
diff = self._collect()
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
459
|
+
|
|
460
|
+
def _is_change_allowed(
|
|
461
|
+
table: str, row_id: str, field: str | None, after_value: Any
|
|
462
|
+
) -> bool:
|
|
463
|
+
"""Check if a change is in the allowed list using semantic comparison."""
|
|
464
|
+
for allowed in allowed_changes:
|
|
465
|
+
allowed_pk = allowed.get("pk")
|
|
466
|
+
# Handle type conversion for primary key comparison
|
|
467
|
+
# Convert both to strings for comparison to handle int/string mismatches
|
|
468
|
+
pk_match = (
|
|
469
|
+
str(allowed_pk) == str(row_id) if allowed_pk is not None else False
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if (
|
|
473
|
+
allowed["table"] == table
|
|
474
|
+
and pk_match
|
|
475
|
+
and allowed.get("field") == field
|
|
476
|
+
and _values_equivalent(allowed.get("after"), after_value)
|
|
477
|
+
):
|
|
478
|
+
return True
|
|
479
|
+
return False
|
|
437
480
|
|
|
438
481
|
# Collect all unexpected changes for detailed reporting
|
|
439
482
|
unexpected_changes = []
|
|
@@ -443,8 +486,7 @@ class SnapshotDiff:
|
|
|
443
486
|
for f, vals in row["changes"].items():
|
|
444
487
|
if self.ignore_config.should_ignore_field(tbl, f):
|
|
445
488
|
continue
|
|
446
|
-
|
|
447
|
-
if tup not in allowed_set:
|
|
489
|
+
if not _is_change_allowed(tbl, row["row_id"], f, vals["after"]):
|
|
448
490
|
unexpected_changes.append(
|
|
449
491
|
{
|
|
450
492
|
"type": "modification",
|
|
@@ -458,8 +500,7 @@ class SnapshotDiff:
|
|
|
458
500
|
)
|
|
459
501
|
|
|
460
502
|
for row in report.get("added_rows", []):
|
|
461
|
-
|
|
462
|
-
if tup not in allowed_set:
|
|
503
|
+
if not _is_change_allowed(tbl, row["row_id"], None, "__added__"):
|
|
463
504
|
unexpected_changes.append(
|
|
464
505
|
{
|
|
465
506
|
"type": "insertion",
|
|
@@ -472,8 +513,7 @@ class SnapshotDiff:
|
|
|
472
513
|
)
|
|
473
514
|
|
|
474
515
|
for row in report.get("removed_rows", []):
|
|
475
|
-
|
|
476
|
-
if tup not in allowed_set:
|
|
516
|
+
if not _is_change_allowed(tbl, row["row_id"], None, "__removed__"):
|
|
477
517
|
unexpected_changes.append(
|
|
478
518
|
{
|
|
479
519
|
"type": "deletion",
|
|
@@ -663,4 +703,4 @@ class DatabaseSnapshot:
|
|
|
663
703
|
|
|
664
704
|
# ---------------------------------------------------------------------
|
|
665
705
|
def __repr__(self):
|
|
666
|
-
return f"<DatabaseSnapshot {self.name} at {self.db_path}>"
|
|
706
|
+
return f"<DatabaseSnapshot {self.name} at {self.db_path}>"
|
fleet/verifiers/sql_differ.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fleet-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Python SDK for Fleet environments
|
|
5
5
|
Author-email: Fleet AI <nic@fleet.so>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -31,6 +31,8 @@ Requires-Dist: black>=22.0.0; extra == "dev"
|
|
|
31
31
|
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
32
32
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
33
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
34
|
+
Provides-Extra: playwright
|
|
35
|
+
Requires-Dist: playwright>=1.40.0; extra == "playwright"
|
|
34
36
|
Dynamic: license-file
|
|
35
37
|
|
|
36
38
|
# Fleet SDK
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
examples/dsl_example.py,sha256=W-g0RR7qOTGUSHYiFhcwL3MsrHB-C6t3UHQg6XTxxss,5259
|
|
2
|
+
examples/example.py,sha256=G_UC4fD55QKPPMAKINtsJs0U6b0wg-GbpaeIGwLnhWw,933
|
|
3
|
+
examples/json_tasks_example.py,sha256=fz1hGaJ_VukH2gwJFvhQmyWRO8ocN9ptFnplTJSOrbA,2348
|
|
4
|
+
examples/nova_act_example.py,sha256=EwTivGRpJ4ZGicJw0eK46lvjgjN0_x3FJavafzdVkfc,777
|
|
5
|
+
examples/openai_example.py,sha256=ebaGIs1OFhohUdtQ0fHcMUqvmCd0vJa45_FUo58eTh4,8486
|
|
6
|
+
examples/openai_simple_example.py,sha256=16u_g9vD8mHNTZhta6Qdq_qAQs-wU6FaPVkhGUPnSIs,1788
|
|
7
|
+
examples/quickstart.py,sha256=lVRzbnWIweU9ioe7uk6R2Rm7oSpt4mt8Jq_VUUp1zKg,4696
|
|
8
|
+
fleet/__init__.py,sha256=AjkbRVYzfIUYEw1PvCceGhHBCPOu_c6Qx5KweoNmAQM,1747
|
|
9
|
+
fleet/base.py,sha256=5JnzMMyT1Ey8SrYs4Ydka3bUoYRRA-wxHx1yCURAM48,2009
|
|
10
|
+
fleet/client.py,sha256=RFOEhxJa_de3N6JP48p8xMYhsGVRv5V4U4OWINKpQ8Y,7392
|
|
11
|
+
fleet/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
|
|
12
|
+
fleet/models.py,sha256=Jf6Zmk689TPXhTSnVENK_VCw0VsujWzEWsN3T29MQ0k,3713
|
|
13
|
+
fleet/playwright.py,sha256=5MLFPE5P_-MpzAQ3EJ6GsLthuJiWwYkNuvhPE_rwe_E,8914
|
|
14
|
+
fleet/env/__init__.py,sha256=_zcEf-sukzVblsTfzQbc9ixDC47bsTts2fHlKyu3OMc,81
|
|
15
|
+
fleet/env/client.py,sha256=I2ja8upwdRcBnehn4GRtkRgWPCLz0cJpbSvQmSuKVrc,423
|
|
16
|
+
fleet/instance/__init__.py,sha256=pKuX6zPpTKmJD0c-Qg_hRdnQuiWNohQdYhlHElxilQE,562
|
|
17
|
+
fleet/instance/base.py,sha256=bm6BGd81TnTDqE_qG6S50Xhikf9DwNqEEk1uKFY7dEk,1540
|
|
18
|
+
fleet/instance/client.py,sha256=Tybjm0jbxM_lnTvQ-Gtm2e8u2qvk9vjDfBwns-DpQhw,9872
|
|
19
|
+
fleet/instance/models.py,sha256=ZTiue0YOuhuwX8jYfJAoCzGfqjLqqXRLqK1LVFhq6rQ,4183
|
|
20
|
+
fleet/resources/base.py,sha256=203gD54NP1IvjuSqFo-f7FvrkhtjChggtzrxJK7xf2E,667
|
|
21
|
+
fleet/resources/browser.py,sha256=x11y4aKHogIEv83FByHtExerjV-cDWI3U62349Guq_Q,1368
|
|
22
|
+
fleet/resources/sqlite.py,sha256=sRiII_qJ8X6-FSemlBsXThz4ZPjkNy9wDT8g5UAz2XM,1501
|
|
23
|
+
fleet/verifiers/__init__.py,sha256=sNlURjry8FYFa-0qFy6boRCcNjXAtMPFaAfpaRqpOPk,394
|
|
24
|
+
fleet/verifiers/code.py,sha256=YB96SPncu9emO-udQh7zYf_UR1aPz7nBjPtKf5O6fD8,4356
|
|
25
|
+
fleet/verifiers/db.py,sha256=tssmvJjDHuBIy8qlL_P5-UdmEFUw2DZcqLsWZ8ot3Xw,27766
|
|
26
|
+
fleet/verifiers/sql_differ.py,sha256=dmiGCFXVMEMbAX519OjhVqgA8ZvhnvdmC1BVpL7QCF0,6490
|
|
27
|
+
fleet_python-0.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
28
|
+
fleet_python-0.2.3.dist-info/METADATA,sha256=jdYK3MqjugV1mQ4Bl0Uw-ZH1HQL6Vzr0vTFcnP2xATw,3153
|
|
29
|
+
fleet_python-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
fleet_python-0.2.3.dist-info/top_level.txt,sha256=AOyXOrBXUjPcH4BumElz_D95kiWKNIpUbUPFP_9gCLk,15
|
|
31
|
+
fleet_python-0.2.3.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
examples/dsl_example.py,sha256=LKqm2f2BHZh3Z3MWhpi4L87l5qt2TR0Ol1Mdb4mQ3bA,3297
|
|
2
|
-
examples/example.py,sha256=G_UC4fD55QKPPMAKINtsJs0U6b0wg-GbpaeIGwLnhWw,933
|
|
3
|
-
examples/nova_act_example.py,sha256=Z-LtSpAP4QCRg4zh-e-MQw5L8bdfZ6ev3fB5ExYToao,6443
|
|
4
|
-
examples/openai_example.py,sha256=BWiDQrQCcyKgveVBXmyD7V2xypoPnumF5T5WvuOCpJ0,15258
|
|
5
|
-
examples/quickstart.py,sha256=m5i5gJ0RdHJgBUlOeUSkLPHjj2GVhNvm7BP6g_A9mzA,4691
|
|
6
|
-
fleet/__init__.py,sha256=D8aNOSGc-kUGrgPTVGtRhmz_Lov5yyqSX9-iBQv6Y4A,1361
|
|
7
|
-
fleet/base.py,sha256=5JnzMMyT1Ey8SrYs4Ydka3bUoYRRA-wxHx1yCURAM48,2009
|
|
8
|
-
fleet/client.py,sha256=5QKsCSmVhR10lcEIky5heL2NFZbmSvej19VXtfVuq14,6980
|
|
9
|
-
fleet/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
|
|
10
|
-
fleet/models.py,sha256=Jf6Zmk689TPXhTSnVENK_VCw0VsujWzEWsN3T29MQ0k,3713
|
|
11
|
-
fleet/env/__init__.py,sha256=_zcEf-sukzVblsTfzQbc9ixDC47bsTts2fHlKyu3OMc,81
|
|
12
|
-
fleet/env/client.py,sha256=I2ja8upwdRcBnehn4GRtkRgWPCLz0cJpbSvQmSuKVrc,423
|
|
13
|
-
fleet/manager/__init__.py,sha256=KXs3cz8_524XwG-MwizeEwsaX65HL8ueZieXFb7RItw,467
|
|
14
|
-
fleet/manager/base.py,sha256=bm6BGd81TnTDqE_qG6S50Xhikf9DwNqEEk1uKFY7dEk,1540
|
|
15
|
-
fleet/manager/client.py,sha256=s0EZcFolQyrTscbWVnvcVJpovMmWQFBR-NOBNFjgSBY,8571
|
|
16
|
-
fleet/manager/models.py,sha256=oSWZ893tRhPTLyPybrk1c5AOYduWEGIKEGZjUQa1vGw,3910
|
|
17
|
-
fleet/resources/base.py,sha256=eFo3wnzfPt6UoMxZSLdklvnPTDgqvjS870u8RcOEAvU,666
|
|
18
|
-
fleet/resources/browser.py,sha256=H8wgabP4lxSoN2q3iFQ35qYU3nwSG-gOVQcQ8ot3DqU,1522
|
|
19
|
-
fleet/resources/sqlite.py,sha256=Y30EcelvRGip0jyFEr9wxt3_EfwWS_Edt3O_qiha-pU,1498
|
|
20
|
-
fleet/verifiers/__init__.py,sha256=CLw9-6bshvX7PgLcQ5KYCG9hmCLx83pi3ZS4FZPauAU,163
|
|
21
|
-
fleet/verifiers/database_snapshot.py,sha256=8-DkYVEvHCwBEhKKAfMJBQmE2146z2aZvIaRtZRCk1s,26147
|
|
22
|
-
fleet/verifiers/sql_differ.py,sha256=4o6Gt9472x8rcbhA58OMXdaczJiWjpYwR7LeB32D4QE,6489
|
|
23
|
-
fleet_python-0.2.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
24
|
-
fleet_python-0.2.2.dist-info/METADATA,sha256=qwWJ_66gEeJeWSDcQt8SGkCNJfB0-6Y6dqFXyDDRu-0,3069
|
|
25
|
-
fleet_python-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
-
fleet_python-0.2.2.dist-info/top_level.txt,sha256=AOyXOrBXUjPcH4BumElz_D95kiWKNIpUbUPFP_9gCLk,15
|
|
27
|
-
fleet_python-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|