fleet-python 0.2.75b1__tar.gz → 0.2.75b3__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.
- {fleet_python-0.2.75b1/fleet_python.egg-info → fleet_python-0.2.75b3}/PKG-INFO +1 -1
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/iterate_verifiers.py +6 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/resources/sqlite.py +156 -84
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/verifiers/verifier.py +1 -19
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/resources/sqlite.py +156 -84
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/db.py +153 -77
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/verifier.py +1 -19
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3/fleet_python.egg-info}/PKG-INFO +1 -1
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/pyproject.toml +1 -1
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_expect_only.py +283 -16
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/LICENSE +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/README.md +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/diff_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/dsl_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/exampleResume.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_account.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_action_log.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_mcp_anthropic.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_mcp_openai.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_sync.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_task.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/example_verifier.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/export_tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/fetch_tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/gemini_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/import_tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/json_tasks_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/nova_act_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/openai_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/openai_simple_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/query_builder_example.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/quickstart.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/examples/test_cdp_logging.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/env/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/env/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/exceptions.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/global_client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/instance/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/instance/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/instance/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/models.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/resources/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/resources/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/resources/browser.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/resources/mcp.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/verifiers/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/_async/verifiers/bundler.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/config.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/env/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/env/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/exceptions.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/global_client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/instance/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/instance/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/instance/client.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/instance/models.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/models.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/resources/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/resources/base.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/resources/browser.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/resources/mcp.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/tasks.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/types.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/bundler.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/code.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/decorator.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/parse.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet/verifiers/sql_differ.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet_python.egg-info/SOURCES.txt +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet_python.egg-info/dependency_links.txt +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet_python.egg-info/requires.txt +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/fleet_python.egg-info/top_level.txt +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/scripts/fix_sync_imports.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/scripts/unasync.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/setup.cfg +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/__init__.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_app_method.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_instance_dispatch.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_sqlite_resource_dual_mode.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_sqlite_shared_memory_behavior.py +0 -0
- {fleet_python-0.2.75b1 → fleet_python-0.2.75b3}/tests/test_verifier_from_string.py +0 -0
|
@@ -387,6 +387,12 @@ def apply_verifiers_to_json(json_path: str, python_path: str) -> None:
|
|
|
387
387
|
|
|
388
388
|
if task_key in verifiers:
|
|
389
389
|
new_code = verifiers[task_key]
|
|
390
|
+
|
|
391
|
+
# Escape newlines in debug print patterns (>>> and <<<)
|
|
392
|
+
# These should be \n escape sequences, not actual newlines
|
|
393
|
+
new_code = new_code.replace(">>>\n", ">>>\\n")
|
|
394
|
+
new_code = new_code.replace("\n<<<", "\\n<<<")
|
|
395
|
+
|
|
390
396
|
old_code = task.get("verifier_func", "").strip()
|
|
391
397
|
|
|
392
398
|
# Only update if the code actually changed
|
|
@@ -896,23 +896,28 @@ class AsyncSnapshotDiff:
|
|
|
896
896
|
):
|
|
897
897
|
"""Validate a collected diff against allowed changes with field-level spec support.
|
|
898
898
|
|
|
899
|
-
This version supports:
|
|
900
|
-
1.
|
|
899
|
+
This version supports explicit change types via the "type" field:
|
|
900
|
+
1. Insert specs: {"table": "t", "pk": 1, "type": "insert", "fields": [("name", "value"), ("status", ...)]}
|
|
901
901
|
- ("name", value): check that field equals value
|
|
902
902
|
- ("name", None): check that field is SQL NULL
|
|
903
903
|
- ("name", ...): don't check the value, just acknowledge the field exists
|
|
904
|
-
2.
|
|
905
|
-
-
|
|
906
|
-
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
-
|
|
910
|
-
|
|
904
|
+
2. Modify specs: {"table": "t", "pk": 1, "type": "modify", "resulting_fields": [...], "no_other_changes": True/False}
|
|
905
|
+
- Uses "resulting_fields" (not "fields") to be explicit about what's being checked
|
|
906
|
+
- "no_other_changes" is REQUIRED and must be True or False:
|
|
907
|
+
- True: Every changed field must be in resulting_fields (strict mode)
|
|
908
|
+
- False: Only check fields in resulting_fields match, ignore other changes
|
|
909
|
+
- ("field_name", value): check that after value equals value
|
|
910
|
+
- ("field_name", None): check that after value is SQL NULL
|
|
911
|
+
- ("field_name", ...): don't check value, just acknowledge field changed
|
|
912
|
+
3. Delete specs:
|
|
913
|
+
- Without field validation: {"table": "t", "pk": 1, "type": "delete"}
|
|
914
|
+
- With field validation: {"table": "t", "pk": 1, "type": "delete", "fields": [...]}
|
|
915
|
+
4. Whole-row specs (legacy):
|
|
911
916
|
- For additions: {"table": "t", "pk": 1, "fields": None, "after": "__added__"}
|
|
912
917
|
- For deletions: {"table": "t", "pk": 1, "fields": None, "after": "__removed__"}
|
|
913
918
|
|
|
914
|
-
When using "fields", every field
|
|
915
|
-
|
|
919
|
+
When using "fields" for inserts, every field must be accounted for in the list.
|
|
920
|
+
For modifications, use "resulting_fields" with explicit "no_other_changes".
|
|
916
921
|
For deletions with "fields", all specified fields are validated against the deleted row.
|
|
917
922
|
"""
|
|
918
923
|
|
|
@@ -942,28 +947,41 @@ class AsyncSnapshotDiff:
|
|
|
942
947
|
return True
|
|
943
948
|
return False
|
|
944
949
|
|
|
945
|
-
def
|
|
946
|
-
table: str, row_id: Any,
|
|
950
|
+
def _get_fields_spec_for_type(
|
|
951
|
+
table: str, row_id: Any, change_type: str
|
|
947
952
|
) -> Optional[List[Tuple[str, Any]]]:
|
|
948
|
-
"""Get the bulk fields spec for a given table/row if it exists.
|
|
953
|
+
"""Get the bulk fields spec for a given table/row/type if it exists.
|
|
954
|
+
|
|
955
|
+
Args:
|
|
956
|
+
table: The table name
|
|
957
|
+
row_id: The primary key value
|
|
958
|
+
change_type: One of "insert", "modify", or "delete"
|
|
959
|
+
|
|
960
|
+
Note: For "modify" type, use _get_modify_spec instead.
|
|
961
|
+
"""
|
|
949
962
|
for allowed in allowed_changes:
|
|
950
963
|
allowed_pk = allowed.get("pk")
|
|
951
964
|
pk_match = (
|
|
952
965
|
str(allowed_pk) == str(row_id) if allowed_pk is not None else False
|
|
953
966
|
)
|
|
954
|
-
if
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
if allowed.get("before") is not True:
|
|
962
|
-
return allowed["fields"]
|
|
967
|
+
if (
|
|
968
|
+
allowed["table"] == table
|
|
969
|
+
and pk_match
|
|
970
|
+
and allowed.get("type") == change_type
|
|
971
|
+
and "fields" in allowed
|
|
972
|
+
):
|
|
973
|
+
return allowed["fields"]
|
|
963
974
|
return None
|
|
964
975
|
|
|
965
|
-
def
|
|
966
|
-
"""
|
|
976
|
+
def _get_modify_spec(table: str, row_id: Any) -> Optional[Dict[str, Any]]:
|
|
977
|
+
"""Get the modify spec for a given table/row if it exists.
|
|
978
|
+
|
|
979
|
+
Returns the full spec dict containing:
|
|
980
|
+
- resulting_fields: List of field tuples
|
|
981
|
+
- no_other_changes: Boolean (required)
|
|
982
|
+
|
|
983
|
+
Returns None if no modify spec found.
|
|
984
|
+
"""
|
|
967
985
|
for allowed in allowed_changes:
|
|
968
986
|
allowed_pk = allowed.get("pk")
|
|
969
987
|
pk_match = (
|
|
@@ -972,7 +990,22 @@ class AsyncSnapshotDiff:
|
|
|
972
990
|
if (
|
|
973
991
|
allowed["table"] == table
|
|
974
992
|
and pk_match
|
|
975
|
-
and allowed.get("
|
|
993
|
+
and allowed.get("type") == "modify"
|
|
994
|
+
):
|
|
995
|
+
return allowed
|
|
996
|
+
return None
|
|
997
|
+
|
|
998
|
+
def _is_type_allowed(table: str, row_id: Any, change_type: str) -> bool:
|
|
999
|
+
"""Check if a change type is allowed for the given table/row (with or without fields)."""
|
|
1000
|
+
for allowed in allowed_changes:
|
|
1001
|
+
allowed_pk = allowed.get("pk")
|
|
1002
|
+
pk_match = (
|
|
1003
|
+
str(allowed_pk) == str(row_id) if allowed_pk is not None else False
|
|
1004
|
+
)
|
|
1005
|
+
if (
|
|
1006
|
+
allowed["table"] == table
|
|
1007
|
+
and pk_match
|
|
1008
|
+
and allowed.get("type") == change_type
|
|
976
1009
|
):
|
|
977
1010
|
return True
|
|
978
1011
|
return False
|
|
@@ -1045,19 +1078,28 @@ class AsyncSnapshotDiff:
|
|
|
1045
1078
|
table: str,
|
|
1046
1079
|
row_id: Any,
|
|
1047
1080
|
row_changes: Dict[str, Dict[str, Any]],
|
|
1048
|
-
|
|
1081
|
+
resulting_fields: List[Tuple[str, Any]],
|
|
1082
|
+
no_other_changes: bool,
|
|
1049
1083
|
) -> Optional[List[Tuple[str, Any, str]]]:
|
|
1050
|
-
"""Validate a modification against a
|
|
1084
|
+
"""Validate a modification against a resulting_fields spec.
|
|
1051
1085
|
|
|
1052
1086
|
Returns None if validation passes, or a list of (field, actual_value, issue)
|
|
1053
1087
|
tuples for mismatches.
|
|
1088
|
+
|
|
1089
|
+
Args:
|
|
1090
|
+
table: The table name
|
|
1091
|
+
row_id: The row primary key
|
|
1092
|
+
row_changes: Dict of field_name -> {"before": ..., "after": ...}
|
|
1093
|
+
resulting_fields: List of field tuples to validate
|
|
1094
|
+
no_other_changes: If True, all changed fields must be in resulting_fields.
|
|
1095
|
+
If False, only validate fields in resulting_fields, ignore others.
|
|
1054
1096
|
|
|
1055
1097
|
Field spec semantics for modifications:
|
|
1056
1098
|
- ("field_name", value): check that after value equals value
|
|
1057
1099
|
- ("field_name", None): check that after value is SQL NULL
|
|
1058
1100
|
- ("field_name", ...): don't check value, just acknowledge field changed
|
|
1059
1101
|
"""
|
|
1060
|
-
spec_map = _parse_fields_spec(
|
|
1102
|
+
spec_map = _parse_fields_spec(resulting_fields)
|
|
1061
1103
|
unmatched_fields: List[Tuple[str, Any, str]] = []
|
|
1062
1104
|
|
|
1063
1105
|
for field_name, vals in row_changes.items():
|
|
@@ -1068,10 +1110,13 @@ class AsyncSnapshotDiff:
|
|
|
1068
1110
|
after_value = vals["after"]
|
|
1069
1111
|
|
|
1070
1112
|
if field_name not in spec_map:
|
|
1071
|
-
# Changed field not in spec
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1113
|
+
# Changed field not in spec
|
|
1114
|
+
if no_other_changes:
|
|
1115
|
+
# Strict mode: all changed fields must be accounted for
|
|
1116
|
+
unmatched_fields.append(
|
|
1117
|
+
(field_name, after_value, "NOT_IN_RESULTING_FIELDS")
|
|
1118
|
+
)
|
|
1119
|
+
# If no_other_changes=False, ignore fields not in spec
|
|
1075
1120
|
else:
|
|
1076
1121
|
should_check, expected_value = spec_map[field_name]
|
|
1077
1122
|
if should_check and not _values_equivalent(
|
|
@@ -1084,20 +1129,6 @@ class AsyncSnapshotDiff:
|
|
|
1084
1129
|
|
|
1085
1130
|
return unmatched_fields if unmatched_fields else None
|
|
1086
1131
|
|
|
1087
|
-
def _get_modification_fields_spec(
|
|
1088
|
-
table: str, row_id: Any
|
|
1089
|
-
) -> Optional[List[Tuple[str, Any]]]:
|
|
1090
|
-
"""Get the bulk fields spec for a modification if it exists."""
|
|
1091
|
-
for allowed in allowed_changes:
|
|
1092
|
-
allowed_pk = allowed.get("pk")
|
|
1093
|
-
pk_match = (
|
|
1094
|
-
str(allowed_pk) == str(row_id) if allowed_pk is not None else False
|
|
1095
|
-
)
|
|
1096
|
-
if allowed["table"] == table and pk_match and "fields" in allowed:
|
|
1097
|
-
# For modifications, accept specs without "before": True
|
|
1098
|
-
if allowed.get("before") is not True:
|
|
1099
|
-
return allowed["fields"]
|
|
1100
|
-
return None
|
|
1101
1132
|
|
|
1102
1133
|
# Collect all unexpected changes for detailed reporting
|
|
1103
1134
|
unexpected_changes = []
|
|
@@ -1106,28 +1137,49 @@ class AsyncSnapshotDiff:
|
|
|
1106
1137
|
for row in report.get("modified_rows", []):
|
|
1107
1138
|
row_changes = row["changes"]
|
|
1108
1139
|
|
|
1109
|
-
# Check for
|
|
1110
|
-
|
|
1111
|
-
if
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
"
|
|
1119
|
-
"
|
|
1120
|
-
"
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
"
|
|
1126
|
-
|
|
1140
|
+
# Check for modify spec with resulting_fields
|
|
1141
|
+
modify_spec = _get_modify_spec(tbl, row["row_id"])
|
|
1142
|
+
if modify_spec is not None:
|
|
1143
|
+
resulting_fields = modify_spec.get("resulting_fields")
|
|
1144
|
+
if resulting_fields is not None:
|
|
1145
|
+
# Validate that no_other_changes is provided
|
|
1146
|
+
if "no_other_changes" not in modify_spec:
|
|
1147
|
+
raise ValueError(
|
|
1148
|
+
f"Modify spec for table '{tbl}' pk={row['row_id']} "
|
|
1149
|
+
f"has 'resulting_fields' but missing required 'no_other_changes' field. "
|
|
1150
|
+
f"Set 'no_other_changes': True to verify no other fields changed, "
|
|
1151
|
+
f"or 'no_other_changes': False to only check the specified fields."
|
|
1152
|
+
)
|
|
1153
|
+
no_other_changes = modify_spec["no_other_changes"]
|
|
1154
|
+
if not isinstance(no_other_changes, bool):
|
|
1155
|
+
raise ValueError(
|
|
1156
|
+
f"Modify spec for table '{tbl}' pk={row['row_id']} "
|
|
1157
|
+
f"has 'no_other_changes' but it must be a boolean (True or False), "
|
|
1158
|
+
f"got {type(no_other_changes).__name__}: {repr(no_other_changes)}"
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
unmatched = _validate_modification_with_fields_spec(
|
|
1162
|
+
tbl, row["row_id"], row_changes, resulting_fields, no_other_changes
|
|
1127
1163
|
)
|
|
1128
|
-
|
|
1164
|
+
if unmatched:
|
|
1165
|
+
unexpected_changes.append(
|
|
1166
|
+
{
|
|
1167
|
+
"type": "modification",
|
|
1168
|
+
"table": tbl,
|
|
1169
|
+
"row_id": row["row_id"],
|
|
1170
|
+
"field": None,
|
|
1171
|
+
"before": None,
|
|
1172
|
+
"after": None,
|
|
1173
|
+
"full_row": row,
|
|
1174
|
+
"unmatched_fields": unmatched,
|
|
1175
|
+
}
|
|
1176
|
+
)
|
|
1177
|
+
continue # Skip to next row
|
|
1178
|
+
else:
|
|
1179
|
+
# Modify spec without resulting_fields - just allow the modification
|
|
1180
|
+
continue # Skip to next row
|
|
1129
1181
|
|
|
1130
|
-
# Fall back to single-field specs
|
|
1182
|
+
# Fall back to single-field specs (legacy)
|
|
1131
1183
|
for f, vals in row_changes.items():
|
|
1132
1184
|
if self.ignore_config.should_ignore_field(tbl, f):
|
|
1133
1185
|
continue
|
|
@@ -1147,8 +1199,8 @@ class AsyncSnapshotDiff:
|
|
|
1147
1199
|
for row in report.get("added_rows", []):
|
|
1148
1200
|
row_data = row.get("data", {})
|
|
1149
1201
|
|
|
1150
|
-
# Check for bulk fields spec
|
|
1151
|
-
fields_spec =
|
|
1202
|
+
# Check for bulk fields spec (type: "insert")
|
|
1203
|
+
fields_spec = _get_fields_spec_for_type(tbl, row["row_id"], "insert")
|
|
1152
1204
|
if fields_spec is not None:
|
|
1153
1205
|
unmatched = _validate_row_with_fields_spec(
|
|
1154
1206
|
tbl, row["row_id"], row_data, fields_spec
|
|
@@ -1167,7 +1219,11 @@ class AsyncSnapshotDiff:
|
|
|
1167
1219
|
)
|
|
1168
1220
|
continue # Skip to next row
|
|
1169
1221
|
|
|
1170
|
-
# Check
|
|
1222
|
+
# Check if insertion is allowed without field validation
|
|
1223
|
+
if _is_type_allowed(tbl, row["row_id"], "insert"):
|
|
1224
|
+
continue # Insertion is allowed, skip to next row
|
|
1225
|
+
|
|
1226
|
+
# Check for whole-row spec (legacy)
|
|
1171
1227
|
whole_row_allowed = _is_change_allowed(
|
|
1172
1228
|
tbl, row["row_id"], None, "__added__"
|
|
1173
1229
|
)
|
|
@@ -1187,8 +1243,8 @@ class AsyncSnapshotDiff:
|
|
|
1187
1243
|
for row in report.get("removed_rows", []):
|
|
1188
1244
|
row_data = row.get("data", {})
|
|
1189
1245
|
|
|
1190
|
-
# Check for bulk fields spec
|
|
1191
|
-
fields_spec =
|
|
1246
|
+
# Check for bulk fields spec (type: "delete")
|
|
1247
|
+
fields_spec = _get_fields_spec_for_type(tbl, row["row_id"], "delete")
|
|
1192
1248
|
if fields_spec is not None:
|
|
1193
1249
|
unmatched = _validate_row_with_fields_spec(
|
|
1194
1250
|
tbl, row["row_id"], row_data, fields_spec
|
|
@@ -1207,11 +1263,11 @@ class AsyncSnapshotDiff:
|
|
|
1207
1263
|
)
|
|
1208
1264
|
continue # Skip to next row
|
|
1209
1265
|
|
|
1210
|
-
# Check
|
|
1211
|
-
if
|
|
1266
|
+
# Check if deletion is allowed without field validation
|
|
1267
|
+
if _is_type_allowed(tbl, row["row_id"], "delete"):
|
|
1212
1268
|
continue # Deletion is allowed, skip to next row
|
|
1213
1269
|
|
|
1214
|
-
# Check for whole-row spec
|
|
1270
|
+
# Check for whole-row spec (legacy)
|
|
1215
1271
|
whole_row_allowed = _is_change_allowed(
|
|
1216
1272
|
tbl, row["row_id"], None, "__removed__"
|
|
1217
1273
|
)
|
|
@@ -1292,27 +1348,43 @@ class AsyncSnapshotDiff:
|
|
|
1292
1348
|
error_lines.append("Allowed changes were:")
|
|
1293
1349
|
if allowed_changes:
|
|
1294
1350
|
for i, allowed in enumerate(allowed_changes[:3], 1):
|
|
1295
|
-
|
|
1296
|
-
|
|
1351
|
+
change_type = allowed.get("type", "unspecified")
|
|
1352
|
+
|
|
1353
|
+
# For modify type, use resulting_fields
|
|
1354
|
+
if change_type == "modify" and "resulting_fields" in allowed and allowed["resulting_fields"] is not None:
|
|
1297
1355
|
fields_summary = ", ".join(
|
|
1298
1356
|
f[0] if len(f) == 1 else f"{f[0]}={'NOT_CHECKED' if f[1] is ... else repr(f[1])}"
|
|
1299
|
-
for f in allowed["
|
|
1357
|
+
for f in allowed["resulting_fields"][:3]
|
|
1300
1358
|
)
|
|
1301
|
-
if len(allowed["
|
|
1302
|
-
fields_summary += (
|
|
1303
|
-
|
|
1304
|
-
|
|
1359
|
+
if len(allowed["resulting_fields"]) > 3:
|
|
1360
|
+
fields_summary += f", ... +{len(allowed['resulting_fields']) - 3} more"
|
|
1361
|
+
no_other = allowed.get("no_other_changes", "NOT_SET")
|
|
1362
|
+
error_lines.append(
|
|
1363
|
+
f" {i}. Table: {allowed.get('table')}, "
|
|
1364
|
+
f"ID: {allowed.get('pk')}, "
|
|
1365
|
+
f"Type: {change_type}, "
|
|
1366
|
+
f"resulting_fields: [{fields_summary}], "
|
|
1367
|
+
f"no_other_changes: {no_other}"
|
|
1368
|
+
)
|
|
1369
|
+
elif "fields" in allowed and allowed["fields"] is not None:
|
|
1370
|
+
# Show bulk fields spec (for insert/delete)
|
|
1371
|
+
fields_summary = ", ".join(
|
|
1372
|
+
f[0] if len(f) == 1 else f"{f[0]}={'NOT_CHECKED' if f[1] is ... else repr(f[1])}"
|
|
1373
|
+
for f in allowed["fields"][:3]
|
|
1374
|
+
)
|
|
1375
|
+
if len(allowed["fields"]) > 3:
|
|
1376
|
+
fields_summary += f", ... +{len(allowed['fields']) - 3} more"
|
|
1305
1377
|
error_lines.append(
|
|
1306
1378
|
f" {i}. Table: {allowed.get('table')}, "
|
|
1307
1379
|
f"ID: {allowed.get('pk')}, "
|
|
1380
|
+
f"Type: {change_type}, "
|
|
1308
1381
|
f"Fields: [{fields_summary}]"
|
|
1309
1382
|
)
|
|
1310
1383
|
else:
|
|
1311
1384
|
error_lines.append(
|
|
1312
1385
|
f" {i}. Table: {allowed.get('table')}, "
|
|
1313
1386
|
f"ID: {allowed.get('pk')}, "
|
|
1314
|
-
f"
|
|
1315
|
-
f"After: {repr(allowed.get('after'))}"
|
|
1387
|
+
f"Type: {change_type}"
|
|
1316
1388
|
)
|
|
1317
1389
|
if len(allowed_changes) > 3:
|
|
1318
1390
|
error_lines.append(
|
|
@@ -232,25 +232,7 @@ Remote traceback:
|
|
|
232
232
|
) -> "VerifiersExecuteResponse":
|
|
233
233
|
"""Remote execution of the verifier function that returns the full response model."""
|
|
234
234
|
args_array = list(args)
|
|
235
|
-
|
|
236
|
-
env_data = {
|
|
237
|
-
"instance_id": env.instance_id,
|
|
238
|
-
"env_key": env.env_key,
|
|
239
|
-
"version": env.version,
|
|
240
|
-
"status": env.status,
|
|
241
|
-
"subdomain": env.subdomain,
|
|
242
|
-
"created_at": env.created_at,
|
|
243
|
-
"updated_at": env.updated_at,
|
|
244
|
-
"terminated_at": env.terminated_at,
|
|
245
|
-
"team_id": env.team_id,
|
|
246
|
-
"region": env.region,
|
|
247
|
-
"env_variables": env.env_variables,
|
|
248
|
-
"data_key": env.data_key,
|
|
249
|
-
"data_version": env.data_version,
|
|
250
|
-
"urls": env.urls.model_dump() if env.urls else None,
|
|
251
|
-
"health": env.health,
|
|
252
|
-
}
|
|
253
|
-
args_array.append({"env": env_data})
|
|
235
|
+
args_array.append({"env": env.instance_id})
|
|
254
236
|
args = tuple(args_array)
|
|
255
237
|
|
|
256
238
|
try:
|