graphddb-runtime 1.0.1__tar.gz → 1.0.2__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 (56) hide show
  1. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/PKG-INFO +1 -1
  2. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/runtime.py +25 -0
  3. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime.egg-info/PKG-INFO +1 -1
  4. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/pyproject.toml +1 -1
  5. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_atomic_add_command.py +16 -0
  6. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_unit.py +8 -4
  7. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/README.md +0 -0
  8. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/__init__.py +0 -0
  9. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/async_runtime.py +0 -0
  10. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/batch.py +0 -0
  11. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/behavior_exec.py +0 -0
  12. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/concurrency.py +0 -0
  13. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/cursor.py +0 -0
  14. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/errors.py +0 -0
  15. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/filters.py +0 -0
  16. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/handler_registry.py +0 -0
  17. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/hydration.py +0 -0
  18. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/limits.py +0 -0
  19. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/middleware.py +0 -0
  20. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/per_key_cursor.py +0 -0
  21. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/templates.py +0 -0
  22. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime/transactions.py +0 -0
  23. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
  24. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
  25. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime.egg-info/requires.txt +0 -0
  26. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/graphddb_runtime.egg-info/top_level.txt +0 -0
  27. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/setup.cfg +0 -0
  28. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_concurrency.py +0 -0
  29. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_contract_runtime.py +0 -0
  30. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_crosslang_python_cells.py +0 -0
  31. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_description.py +0 -0
  32. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_description_followups.py +0 -0
  33. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_description_jsdoc.py +0 -0
  34. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_error_policy.py +0 -0
  35. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_generated_module.py +0 -0
  36. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_guard_eval.py +0 -0
  37. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_handler_registry.py +0 -0
  38. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration.py +0 -0
  39. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_batch_write.py +0 -0
  40. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_binding_contract.py +0 -0
  41. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_command.py +0 -0
  42. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_compose.py +0 -0
  43. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_contract.py +0 -0
  44. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_edge_derive.py +0 -0
  45. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_edge_write.py +0 -0
  46. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_events.py +0 -0
  47. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_maintain.py +0 -0
  48. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_middleware.py +0 -0
  49. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_referential.py +0 -0
  50. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_relations.py +0 -0
  51. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_integration_unique.py +0 -0
  52. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_maintain.py +0 -0
  53. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_middleware.py +0 -0
  54. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_relations.py +0 -0
  55. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_spec_version_guard.py +0 -0
  56. {graphddb_runtime-1.0.1 → graphddb_runtime-1.0.2}/tests/test_ttl.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 1.0.1
3
+ Version: 1.0.2
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
@@ -2327,6 +2327,31 @@ class GraphDDBRuntime:
2327
2327
  command_id, spec, params, names, values, sets
2328
2328
  )
2329
2329
 
2330
+ # #333: an UpdateItem that CREATES a row (create-if-absent) must carry the
2331
+ # model's key-source field attributes, like PutItem does. SET each field a
2332
+ # `keyCondition` template references from the command's key params. A
2333
+ # key-source field is immutable for a given key, so a plain SET to the
2334
+ # key-derived value is a safe no-op on existing rows and populates it on
2335
+ # create. Without this, a row first created by an update lacks e.g. `child`,
2336
+ # so a projection of that field returns empty. (Skipped when add_only — the
2337
+ # #301 fallback only fires on rows that already exist.)
2338
+ seen_ks: set = set()
2339
+ for tmpl in spec["keyCondition"].values():
2340
+ for m in _PLACEHOLDER_RE.finditer(str(tmpl)):
2341
+ field = m.group(0)[1:-1]
2342
+ if "." in field or field in seen_ks or field not in params:
2343
+ continue
2344
+ raw = params[field]
2345
+ if raw is None:
2346
+ continue
2347
+ seen_ks.add(field)
2348
+ idx = len(seen_ks) - 1
2349
+ n = f"#ks{idx}"
2350
+ v = f":ks{idx}"
2351
+ names[n] = field
2352
+ values[v] = self._serializer.serialize(raw)
2353
+ sets.append(f"{n} = {v}")
2354
+
2330
2355
  # #301: atomic-`ADD` clauses (`ADD #a :d`, a caller `param.number()` delta).
2331
2356
  # Built AFTER the SET so an ADD-only expression (`add_only`, or an update
2332
2357
  # whose SET change set is empty) is legal — DynamoDB's `SET … ADD …` is one
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 1.0.1
3
+ Version: 1.0.2
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 = "1.0.1"
7
+ version = "1.0.2"
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"
@@ -168,6 +168,22 @@ def test_mark_fallback_add_only_on_condition_fail():
168
168
  assert any(v == {"N": "1"} for v in fvals.values()), fvals
169
169
 
170
170
 
171
+ def test_mark_stores_key_source_fields():
172
+ """#333: an UpdateItem that CREATES the row must persist the key-source field
173
+ attributes (part/child) via a plain SET — like PutItem — so a row first created
174
+ by the mark carries them and a projection of e.g. ``child`` is not empty."""
175
+ client = _MarkClient(fail_first=False)
176
+ rt = _mark_runtime(client)
177
+
178
+ rt.execute_command("StatsTreeCmd__mark", _MARK_PARAMS)
179
+
180
+ req = client.updates[0]
181
+ assert _has_set_clause(req["UpdateExpression"]), req["UpdateExpression"]
182
+ names = set(req["ExpressionAttributeNames"].values())
183
+ for field in ("part", "child"):
184
+ assert field in names, f"key-source field {field!r} must be SET (#333); {names}"
185
+
186
+
171
187
  def test_mark_non_ccf_error_propagates():
172
188
  """A NON-condition error on the primary must NOT be swallowed by the two-stage —
173
189
  it propagates and no ADD-only fallback fires. Parity with the Rust
@@ -253,9 +253,12 @@ def test_update_item_builds_set_expression():
253
253
  method, req = client.calls[0]
254
254
  assert method == "update_item"
255
255
  assert plain(req["Key"]) == {"PK": "USER#alice", "SK": "PROFILE"}
256
- assert req["UpdateExpression"] == "SET #c0 = :c0"
257
- assert req["ExpressionAttributeNames"] == {"#c0": "status"}
258
- assert plain(req["ExpressionAttributeValues"]) == {":c0": "disabled"}
256
+ # #333: the update also SETs the key-source field(s) the PK/SK templates
257
+ # reference (here `userId`, from `USER#{userId}`) so a row first created by this
258
+ # update carries them like a PutItem would — a no-op on an existing row.
259
+ assert req["UpdateExpression"] == "SET #c0 = :c0, #ks0 = :ks0"
260
+ assert req["ExpressionAttributeNames"] == {"#c0": "status", "#ks0": "userId"}
261
+ assert plain(req["ExpressionAttributeValues"]) == {":c0": "disabled", ":ks0": "alice"}
259
262
 
260
263
 
261
264
  def test_update_item_rederives_affected_gsi_key():
@@ -303,7 +306,8 @@ def test_update_item_does_not_touch_unaffected_gsi():
303
306
  rt.execute_command("UserConfCommands__disableUser", {"userId": "alice", "status": "disabled"})
304
307
  _method, req = client.calls[0]
305
308
  assert "GSI1PK" not in req["ExpressionAttributeNames"].values()
306
- assert req["UpdateExpression"] == "SET #c0 = :c0"
309
+ # #333: the change SET plus the key-source SET (`userId`); no GSI re-derive.
310
+ assert req["UpdateExpression"] == "SET #c0 = :c0, #ks0 = :ks0"
307
311
 
308
312
 
309
313
  def test_put_item_injects_key_attributes():