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.

@@ -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}" for l, r in onmap.items() # noqa: E741
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
- allowed_set = {
434
- (c["table"], c.get("pk"), c.get("field"), c.get("after"))
435
- for c in allowed_changes
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
- tup = (tbl, row["row_id"], f, vals["after"])
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
- tup = (tbl, row["row_id"], None, "__added__")
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
- tup = (tbl, row["row_id"], None, "__removed__")
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}>"
@@ -184,4 +184,4 @@ class SQLiteDiffer:
184
184
  except Exception as e:
185
185
  results[table] = {"error": str(e)}
186
186
 
187
- return results
187
+ return results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.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