graphddb-runtime 0.2.2__tar.gz → 0.2.4__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.
Files changed (37) hide show
  1. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/PKG-INFO +1 -1
  2. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/transactions.py +16 -4
  3. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime.egg-info/PKG-INFO +1 -1
  4. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/pyproject.toml +1 -1
  5. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_unit.py +55 -0
  6. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/README.md +0 -0
  7. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/__init__.py +0 -0
  8. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/async_runtime.py +0 -0
  9. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/batch.py +0 -0
  10. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/concurrency.py +0 -0
  11. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/cursor.py +0 -0
  12. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/errors.py +0 -0
  13. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/filters.py +0 -0
  14. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/hydration.py +0 -0
  15. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/limits.py +0 -0
  16. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/per_key_cursor.py +0 -0
  17. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/relations.py +0 -0
  18. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/runtime.py +0 -0
  19. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime/templates.py +0 -0
  20. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
  21. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
  22. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime.egg-info/requires.txt +0 -0
  23. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/graphddb_runtime.egg-info/top_level.txt +0 -0
  24. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/setup.cfg +0 -0
  25. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_concurrency.py +0 -0
  26. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_contract_runtime.py +0 -0
  27. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration.py +0 -0
  28. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_command.py +0 -0
  29. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_compose.py +0 -0
  30. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_contract.py +0 -0
  31. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_edge_derive.py +0 -0
  32. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_edge_write.py +0 -0
  33. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_events.py +0 -0
  34. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_referential.py +0 -0
  35. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_relations.py +0 -0
  36. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_integration_unique.py +0 -0
  37. {graphddb_runtime-0.2.2 → graphddb_runtime-0.2.4}/tests/test_relations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Thin DynamoDB executor for GraphDDB-generated Python repositories (single-operation core, issue #44).
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -251,6 +251,12 @@ def _collapse_same_key_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]
251
251
  the ADD — the exact silent-drop #93 forbids — so this and any other unhandled same-key
252
252
  op combination raise rather than collapse.
253
253
 
254
+ The ``Delete``+``Put`` and ``Put``+``Update`` branches each require the bucket be EXACTLY
255
+ that pair (no third op kind). A 3-way ``Delete``+``Put``+``Update`` collision (a
256
+ hand-written self-contradictory transaction that deletes, puts, AND increments one row)
257
+ is therefore NOT absorbed by the swap no-op nor the Put+Update reject — it reaches the
258
+ catch-all loud reject below rather than silently dropping the ``Update`` (issue #96).
259
+
254
260
  A genuine swap (``old != new``) resolves to two distinct keys, so both survive.
255
261
  First-seen order is preserved (a merged ADD takes the position of the FIRST update
256
262
  in its bucket), so the output is identical to the TS runtimes' for the same SSoT.
@@ -267,11 +273,17 @@ def _collapse_same_key_items(items: List[Dict[str, Any]]) -> List[Dict[str, Any]
267
273
  out.append(item)
268
274
  continue
269
275
  kinds = {_item_kind(b) for b in bucket}
270
- if "Delete" in kinds and "Put" in kinds:
271
- # Swap pair resolved to one key → net no-op: drop EVERY member at this key.
276
+ if kinds == {"Delete", "Put"}:
277
+ # EXACTLY {Delete, Put} — a swap pair resolved to one key → net no-op: drop
278
+ # EVERY member at this key. The exact-set guard is load-bearing: a 3-way bucket
279
+ # that ALSO carries an Update (a hand-written self-contradictory transaction
280
+ # that deletes, puts, AND increments the SAME row) is NOT a swap no-op —
281
+ # dropping it here would silently swallow the Update. Such a 3-way collision
282
+ # falls through to the catch-all reject below (issue #96).
272
283
  continue
273
- if "Put" in kinds and "Update" in kinds:
274
- # Base entity Put + derived counter Update(ADD) on the SAME row (issue #93,
284
+ if kinds == {"Put", "Update"}:
285
+ # EXACTLY {Put, Update}: base entity Put + derived counter Update(ADD) on the
286
+ # SAME row (issue #93,
275
287
  # the aliased self-target that evades the build-time guard). A Put+Update on
276
288
  # one item is unsupported (DynamoDB rejects touching one key twice) and
277
289
  # collapsing it would silently drop the increment — FAIL LOUDLY instead.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Thin DynamoDB executor for GraphDDB-generated Python repositories (single-operation core, issue #44).
5
5
  License: MIT
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graphddb-runtime"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Thin DynamoDB executor for GraphDDB-generated Python repositories (single-operation core, issue #44)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -770,6 +770,61 @@ def test_collapse_unhandled_same_key_combination_rejects_loudly():
770
770
  _collapse_same_key_items([delete, update])
771
771
 
772
772
 
773
+ def test_collapse_three_way_delete_put_update_rejects_loudly():
774
+ """A 3-way ``Delete``+``Put``+``Update`` on ONE physical row (a hand-written
775
+ self-contradictory transaction that deletes, puts, AND increments the same row).
776
+ Pre-#96 the ``Delete``+``Put`` swap no-op branch fired first and silently dropped the
777
+ ``Update`` too. The shared rule now requires that branch be EXACTLY {Delete, Put}, so the
778
+ 3-way falls through to the catch-all loud reject. Mirrors the TS ``collapseSameKey``."""
779
+ import pytest
780
+ from graphddb_runtime.transactions import _collapse_same_key_items
781
+
782
+ delete = _delete("T", "GROUP#eng", "META")
783
+ put = _put("T", "GROUP#eng", "META") # SAME key
784
+ add = _add_update("T", "GROUP#eng", "META", "memberCount", 1) # SAME key — the Update
785
+ with pytest.raises(ValueError, match=r"unsupported op combination \(Delete\+Put\+Update\)"):
786
+ _collapse_same_key_items([delete, put, add])
787
+
788
+
789
+ def test_expand_three_way_delete_put_update_rejects_loudly():
790
+ """End-to-end through ``TransactionExpander.expand`` (the public, spec-driven path, the
791
+ Python mirror of the TS ``expandTransaction``): a 3-way ``Delete``+``Put``+``Update``
792
+ resolving to ONE marker row must throw rather than silently drop the ``Update`` (#96)."""
793
+ import pytest
794
+ from graphddb_runtime.transactions import TransactionExpander
795
+
796
+ rt = make_runtime(FakeClient())
797
+ expander = TransactionExpander(rt)
798
+ spec = {
799
+ "items": [
800
+ {
801
+ "type": "Delete",
802
+ "tableName": "UniqTbl",
803
+ "entity": "__marker__",
804
+ "literalKey": True,
805
+ "keyCondition": {"PK": "ROW#{rowId}", "SK": "META"},
806
+ },
807
+ {
808
+ "type": "Put",
809
+ "tableName": "UniqTbl",
810
+ "entity": "__marker__",
811
+ "literalKey": True,
812
+ "item": {"PK": "ROW#{rowId}", "SK": "META"},
813
+ },
814
+ {
815
+ "type": "Update",
816
+ "tableName": "UniqTbl",
817
+ "entity": "__marker__",
818
+ "literalKey": True,
819
+ "keyCondition": {"PK": "ROW#{rowId}", "SK": "META"},
820
+ "add": {"hits": "1"},
821
+ },
822
+ ]
823
+ }
824
+ with pytest.raises(ValueError, match=r"unsupported op combination \(Delete\+Put\+Update\)"):
825
+ expander.expand(spec, {"rowId": "r1"})
826
+
827
+
773
828
  def test_expand_collapses_unchanged_unique_guard_swap_to_no_op():
774
829
  """End-to-end through ``TransactionExpander.expand``: a literalKey guard swap whose
775
830
  ``old.* == input.*`` collapses to a net no-op, leaving only the base write — the