fleet-python 0.2.1__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.

@@ -0,0 +1,187 @@
1
+ import sqlite3
2
+ from typing import Any
3
+
4
+
5
+ class SQLiteDiffer:
6
+ def __init__(self, before_db: str, after_db: str):
7
+ self.before_db = before_db
8
+ self.after_db = after_db
9
+
10
+ def get_table_schema(self, db_path: str, table_name: str) -> list[str]:
11
+ """Get column names for a table"""
12
+ conn = sqlite3.connect(db_path)
13
+ cursor = conn.cursor()
14
+ cursor.execute(f"PRAGMA table_info({table_name})")
15
+ columns = [row[1] for row in cursor.fetchall()]
16
+ conn.close()
17
+ return columns
18
+
19
+ def get_primary_key_columns(self, db_path: str, table_name: str) -> list[str]:
20
+ """Get all primary key columns for a table, ordered by their position"""
21
+ conn = sqlite3.connect(db_path)
22
+ cursor = conn.cursor()
23
+ cursor.execute(f"PRAGMA table_info({table_name})")
24
+
25
+ pk_columns = []
26
+ for row in cursor.fetchall():
27
+ # row format: (cid, name, type, notnull, dflt_value, pk)
28
+ if row[5] > 0: # pk > 0 means it's part of primary key
29
+ pk_columns.append((row[5], row[1])) # (pk_position, column_name)
30
+
31
+ conn.close()
32
+
33
+ # Sort by primary key position and return just the column names
34
+ pk_columns.sort(key=lambda x: x[0])
35
+ return [col[1] for col in pk_columns]
36
+
37
+ def get_all_tables(self, db_path: str) -> list[str]:
38
+ """Get all table names from database"""
39
+ conn = sqlite3.connect(db_path)
40
+ cursor = conn.cursor()
41
+ cursor.execute(
42
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
43
+ )
44
+ tables = [row[0] for row in cursor.fetchall()]
45
+ conn.close()
46
+ return tables
47
+
48
+ def get_table_data(
49
+ self,
50
+ db_path: str,
51
+ table_name: str,
52
+ primary_key_columns: list[str] | None = None,
53
+ ) -> tuple[dict[Any, dict], list[str]]:
54
+ """Get table data indexed by primary key (single column or composite)"""
55
+ conn = sqlite3.connect(db_path)
56
+ conn.row_factory = sqlite3.Row
57
+ cursor = conn.cursor()
58
+
59
+ # If no primary key specified, try to detect it
60
+ if primary_key_columns is None:
61
+ primary_key_columns = self.get_primary_key_columns(db_path, table_name)
62
+
63
+ # Fallback strategies if no primary key found
64
+ if not primary_key_columns:
65
+ columns = self.get_table_schema(db_path, table_name)
66
+ if "id" in columns:
67
+ primary_key_columns = ["id"]
68
+ else:
69
+ primary_key_columns = ["rowid"]
70
+
71
+ cursor.execute(f"SELECT rowid, * FROM {table_name}")
72
+ rows = cursor.fetchall()
73
+
74
+ data = {}
75
+ for row in rows:
76
+ row_dict = dict(row)
77
+
78
+ # Create primary key value (single value or tuple for composite keys)
79
+ if len(primary_key_columns) == 1:
80
+ pk_col = primary_key_columns[0]
81
+ if pk_col == "rowid":
82
+ pk_value = row_dict["rowid"]
83
+ else:
84
+ pk_value = row_dict.get(pk_col)
85
+ else:
86
+ # Composite primary key - use tuple of values
87
+ pk_values = []
88
+ for pk_col in primary_key_columns:
89
+ pk_values.append(row_dict.get(pk_col))
90
+ pk_value = tuple(pk_values)
91
+
92
+ if pk_value is not None and (
93
+ not isinstance(pk_value, tuple) or all(v is not None for v in pk_value)
94
+ ):
95
+ data[pk_value] = row_dict
96
+
97
+ conn.close()
98
+ return data, primary_key_columns
99
+
100
+ def compare_rows(self, before_row: dict, after_row: dict) -> dict[str, dict]:
101
+ """Compare two rows field by field"""
102
+ changes = {}
103
+
104
+ all_fields = set(before_row.keys()) | set(after_row.keys())
105
+
106
+ for field in all_fields:
107
+ before_val = before_row.get(field)
108
+ after_val = after_row.get(field)
109
+
110
+ if before_val != after_val:
111
+ changes[field] = {"before": before_val, "after": after_val}
112
+
113
+ return changes
114
+
115
+ def diff_table(
116
+ self, table_name: str, primary_key_columns: list[str] | None = None
117
+ ) -> dict:
118
+ """Create comprehensive diff of a table"""
119
+ before_data, detected_pk = self.get_table_data(
120
+ self.before_db, table_name, primary_key_columns
121
+ )
122
+ after_data, _ = self.get_table_data(
123
+ self.after_db, table_name, primary_key_columns or detected_pk
124
+ )
125
+
126
+ before_keys = set(before_data.keys())
127
+ after_keys = set(after_data.keys())
128
+
129
+ # Find different types of changes
130
+ added_keys = after_keys - before_keys
131
+ removed_keys = before_keys - after_keys
132
+ common_keys = before_keys & after_keys
133
+
134
+ result = {
135
+ "table_name": table_name,
136
+ "primary_key": primary_key_columns or detected_pk,
137
+ "added_rows": [],
138
+ "removed_rows": [],
139
+ "modified_rows": [],
140
+ "unchanged_count": 0,
141
+ "total_changes": 0,
142
+ }
143
+
144
+ # Added rows
145
+ for key in added_keys:
146
+ result["added_rows"].append({"row_id": key, "data": after_data[key]})
147
+
148
+ # Removed rows
149
+ for key in removed_keys:
150
+ result["removed_rows"].append({"row_id": key, "data": before_data[key]})
151
+
152
+ # Check for modifications in existing rows
153
+ for key in common_keys:
154
+ field_changes = self.compare_rows(before_data[key], after_data[key])
155
+
156
+ if field_changes:
157
+ result["modified_rows"].append(
158
+ {
159
+ "row_id": key,
160
+ "changes": field_changes,
161
+ "before_row": before_data[key],
162
+ "after_row": after_data[key],
163
+ }
164
+ )
165
+ else:
166
+ result["unchanged_count"] += 1
167
+
168
+ result["total_changes"] = (
169
+ len(result["added_rows"])
170
+ + len(result["removed_rows"])
171
+ + len(result["modified_rows"])
172
+ )
173
+
174
+ return result
175
+
176
+ def diff_all_tables(self) -> dict:
177
+ """Diff all tables in the database"""
178
+ tables = self.get_all_tables(self.before_db)
179
+ results = {}
180
+
181
+ for table in tables:
182
+ try:
183
+ results[table] = self.diff_table(table)
184
+ except Exception as e:
185
+ results[table] = {"error": str(e)}
186
+
187
+ return results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.1
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,21 +0,0 @@
1
- examples/example.py,sha256=nF-XyXXYn3eNQQe00DGpagI-bQkh3WRhNPIpQLHfUrc,1502
2
- examples/nova_act_example.py,sha256=Z-LtSpAP4QCRg4zh-e-MQw5L8bdfZ6ev3fB5ExYToao,6443
3
- examples/openai_example.py,sha256=NJZo3nA6_56pYVINcfSOg9OH5JX13SNqzvIG-48SLls,10873
4
- examples/quickstart.py,sha256=AnLlLQYRfrP7y2J69d1HZRlZsjJT-KeBu897MoNfqYM,4671
5
- fleet/__init__.py,sha256=ckXsUDKRzDs4YOFvEhId_wjYBVbFmmj4wuSweE-Rdtk,1314
6
- fleet/base.py,sha256=lgKhPZhLotP4Iqn7Z4nQTfCvuD3bT37MoucTrSRE2zs,2006
7
- fleet/client.py,sha256=xMyKc49YwTAh1F5Wbk5hdaB4F6wlrl41a3EjyBdjFUk,5660
8
- fleet/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
9
- fleet/models.py,sha256=Jf6Zmk689TPXhTSnVENK_VCw0VsujWzEWsN3T29MQ0k,3713
10
- fleet/env/__init__.py,sha256=h46DwQKJZHqPivXTlwE6JIjwJocwXJQsxe4B2M6xUvo,455
11
- fleet/env/base.py,sha256=bm6BGd81TnTDqE_qG6S50Xhikf9DwNqEEk1uKFY7dEk,1540
12
- fleet/env/client.py,sha256=gOfdjaDNilJ2g0snZnR3nDIepD7d8m6QHBQMZNMhsWA,8600
13
- fleet/env/models.py,sha256=oSWZ893tRhPTLyPybrk1c5AOYduWEGIKEGZjUQa1vGw,3910
14
- fleet/resources/base.py,sha256=rzFXBoFqtkM0HZpwqN9NHiwQbmeKOGOYh7G4REaHKGc,553
15
- fleet/resources/browser.py,sha256=3BN8AQXzdd1wFoVfDps_Gyx7QvN2UC4cbjU2ZlnFbIA,1002
16
- fleet/resources/sqlite.py,sha256=I3TnCxU5_WNarNPpZbfppHk2OeDbgp82JlBYGW6DtRs,1486
17
- fleet_python-0.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- fleet_python-0.2.1.dist-info/METADATA,sha256=W1NcdBg98RWQNnedwyUQ_CdUc0BCDxtO1xBcAtu4Sik,3069
19
- fleet_python-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- fleet_python-0.2.1.dist-info/top_level.txt,sha256=AOyXOrBXUjPcH4BumElz_D95kiWKNIpUbUPFP_9gCLk,15
21
- fleet_python-0.2.1.dist-info/RECORD,,
File without changes