graphddb-runtime 1.0.7__tar.gz → 1.0.9__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.7 → graphddb_runtime-1.0.9}/PKG-INFO +2 -2
  2. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/behavior_exec.py +70 -46
  3. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/runtime.py +11 -31
  4. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/transactions.py +0 -30
  5. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime.egg-info/PKG-INFO +2 -2
  6. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime.egg-info/requires.txt +1 -1
  7. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/pyproject.toml +2 -2
  8. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/README.md +0 -0
  9. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/__init__.py +0 -0
  10. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/async_runtime.py +0 -0
  11. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/batch.py +0 -0
  12. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/concurrency.py +0 -0
  13. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/cursor.py +0 -0
  14. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/errors.py +0 -0
  15. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/filters.py +0 -0
  16. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/handler_registry.py +0 -0
  17. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/hydration.py +0 -0
  18. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/limits.py +0 -0
  19. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/middleware.py +0 -0
  20. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/per_key_cursor.py +0 -0
  21. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime/templates.py +0 -0
  22. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
  23. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
  24. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/graphddb_runtime.egg-info/top_level.txt +0 -0
  25. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/setup.cfg +0 -0
  26. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_atomic_add_command.py +0 -0
  27. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_concurrency.py +0 -0
  28. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_contract_runtime.py +0 -0
  29. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_crosslang_python_cells.py +0 -0
  30. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_description.py +0 -0
  31. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_description_followups.py +0 -0
  32. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_description_jsdoc.py +0 -0
  33. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_error_policy.py +0 -0
  34. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_generated_module.py +0 -0
  35. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_guard_eval.py +0 -0
  36. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_handler_registry.py +0 -0
  37. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration.py +0 -0
  38. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_batch_write.py +0 -0
  39. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_binding_contract.py +0 -0
  40. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_command.py +0 -0
  41. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_compose.py +0 -0
  42. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_contract.py +0 -0
  43. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_edge_derive.py +0 -0
  44. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_edge_write.py +0 -0
  45. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_events.py +0 -0
  46. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_maintain.py +0 -0
  47. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_middleware.py +0 -0
  48. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_referential.py +0 -0
  49. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_relations.py +0 -0
  50. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_integration_unique.py +0 -0
  51. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_maintain.py +0 -0
  52. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_middleware.py +0 -0
  53. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_relations.py +0 -0
  54. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_spec_version_guard.py +0 -0
  55. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_ttl.py +0 -0
  56. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.9}/tests/test_unit.py +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 1.0.7
3
+ Version: 1.0.9
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: boto3>=1.26
9
- Requires-Dist: behavior-contracts==0.7.1
9
+ Requires-Dist: behavior-contracts==0.7.3
10
10
  Provides-Extra: test
11
11
  Requires-Dist: pytest>=7.0; extra == "test"
12
12
 
@@ -122,16 +122,22 @@ _READ_META_PORTS = frozenset(
122
122
  "filter",
123
123
  "projection",
124
124
  "cardinality",
125
- "implicitSourceField",
126
125
  )
127
126
  )
128
127
 
129
128
 
130
129
  def _node_parts(node: Mapping[str, Any]) -> Tuple[str, Optional[Mapping[str, Any]], Mapping[str, Any]]:
131
- """``(id, map|None, ports)`` of a body node (componentRef or map)."""
130
+ """``(id, map|None, ports)`` of a body node (componentRef / map / fanout)."""
132
131
  m = node.get("map")
133
132
  if m is not None:
134
133
  return node["id"], m, m["ports"]
134
+ fo = node.get("fanout")
135
+ if fo is not None:
136
+ # A `refs` fan-out (bc 0.7.3 v3): its per-element BatchGet ports drive the
137
+ # SAME batched handler a batched map did. bc's `run_behavior` owns the
138
+ # dedupe/drop/wrap (fanout_dedup_drop) — graphddb only supplies the aligned
139
+ # bodies. Return its ports for the structural/config consumers.
140
+ return node["id"], None, fo["ports"]
135
141
  return node["id"], None, node["ports"]
136
142
 
137
143
 
@@ -240,11 +246,6 @@ def execute_read_component(
240
246
  "is_root": node_id == "root",
241
247
  }
242
248
 
243
- # Batched fan-out bookkeeping for the refs fold: node id → the per-element
244
- # dedupe markers, ALIGNED to the map's driver elements (first-seen dedupe /
245
- # missing-drop needs the element→key identity the handler computed).
246
- refs_markers: Dict[str, List[str]] = {}
247
-
248
249
  def _key_of(ports: Mapping[str, Any], key_attrs: List[str]) -> Dict[str, Any]:
249
250
  return {attr: ports[attr] for attr in key_attrs}
250
251
 
@@ -381,8 +382,12 @@ def execute_read_component(
381
382
  ports["items"] if batched else [ports] # type: ignore[assignment]
382
383
  )
383
384
 
384
- # Dedupe INSIDE the handler (CONSUMER) — first-seen order, exactly the
385
- # old `_run_relation_batch_get` / `_apply_refs` marker dedupe.
385
+ # PHYSICAL dedupe INSIDE the handler (CONSUMER) — the ONE deduped/chunked
386
+ # BatchGet the request needs to avoid over-fetch. The RESULT-level dedupe /
387
+ # dangling-drop / connection-wrap is NO LONGER graphddb's: bc's `fanout`
388
+ # node (v3, run_behavior → fanout_dedup_drop) owns it, keyed off the result
389
+ # body's `dedupeKey` field. The handler returns the list ALIGNED to the
390
+ # input element order (null for a missing body) — the fanout's contract.
386
391
  plain_keys: List[Dict[str, Any]] = []
387
392
  markers: List[str] = []
388
393
  seen: set = set()
@@ -394,8 +399,6 @@ def execute_read_component(
394
399
  continue
395
400
  seen.add(marker)
396
401
  plain_keys.append(plain_key)
397
- if batched:
398
- refs_markers[ctx["nodeId"]] = markers
399
402
 
400
403
  if len(plain_keys) > rt._limits.max_items:
401
404
  raise LimitExceededError(
@@ -491,25 +494,30 @@ def execute_read_component(
491
494
  if exc.code == "OP_FAILED" and ddb_error:
492
495
  raise ddb_error[-1]
493
496
  raise
494
- return _fold_read_output(component, out, refs_markers)
497
+ return _fold_read_output(component, out)
495
498
 
496
499
 
497
500
  def _fold_read_output(
498
501
  component: Mapping[str, Any],
499
502
  out: Any,
500
- refs_markers: Mapping[str, List[str]],
501
503
  ) -> Any:
502
- """The documented mechanical, id-driven fold from the v2 ``run_behavior``
504
+ """The documented mechanical, id-driven fold from the v3 ``run_behavior``
503
505
  output to the old assembled result shape (components.ts module doc; proven
504
506
  golden-equal by ``__tests__/spec/components.test.ts``):
505
507
 
506
508
  - a chained connection's flat per-element ``into`` keys re-nest along the
507
509
  node-id path (``groups.items.group.permissions.items`` → move each
508
510
  element's ``permissions`` under its ``group``);
509
- - a batched refs fan-out's ALIGNED list dedupes by first-seen element key,
510
- drops missing bodies, and wraps as a ``{items, cursor: None}`` connection;
511
- - every ``implicitSourceField`` marker strips the driver attribute from the
512
- assembled root (the old ``_strip_implicit_sources``).
511
+ - a ``refs`` fan-out node's connection ``{items, cursor: None}`` is produced
512
+ ENTIRELY by bc's ``fanout`` node (v3, ``fanout_dedup_drop``): first-seen
513
+ dedupe (by the result body's ``dedupeKey`` field), dangling drop, and the
514
+ connection wrap all happen inside ``run_behavior`` now — graphddb folds it
515
+ through unchanged (the dedup/drop is no longer here);
516
+ - the implicitly-projected ``refs`` SOURCE id-list (``tagRefs``) is stripped
517
+ from the assembled root: it is a bare-return field the fanout ``over`` read,
518
+ absent from the caller-visible ``outputType`` (the annotation omits it), so
519
+ any root field NOT in ``outputType.root`` is dropped (the old
520
+ ``_strip_implicit_sources``, now derived from the type-notation).
513
521
  """
514
522
  output = component["output"]
515
523
  body: List[Mapping[str, Any]] = list(component["body"])
@@ -535,23 +543,10 @@ def _fold_read_output(
535
543
  )
536
544
  val = out_obj[prop]
537
545
  if "ref" in spec_node:
538
- node_id = spec_node["ref"][0]
539
- node = node_by_id[node_id]
540
- m = node.get("map")
541
- if m is not None and "into" not in m:
542
- # Batched refs fan-out: aligned list → dedupe/drop → connection.
543
- markers = refs_markers.get(node_id, [])
544
- seen: set = set()
545
- items: List[Any] = []
546
- for i, item_body in enumerate(val or []):
547
- marker = markers[i]
548
- if item_body is None or marker in seen:
549
- continue
550
- seen.add(marker)
551
- items.append(item_body)
552
- result[prop] = {"items": items, "cursor": None}
553
- else:
554
- result[prop] = val
546
+ # A `refs` fan-out node result is ALREADY the deduped/dropped
547
+ # `{items, cursor: None}` connection (bc's fanout node owns it); a
548
+ # plain relation ref is its value verbatim. Either way: pass through.
549
+ result[prop] = val
555
550
  elif "obj" in spec_node:
556
551
  # Chained connection: {cursor, items:<augmented tail>} — re-nest the
557
552
  # per-element flat `into` keys along each chain map's id.
@@ -565,13 +560,38 @@ def _fold_read_output(
565
560
  f"components[]: unsupported output node for '{prop}' (fail-closed)"
566
561
  )
567
562
 
568
- # Implicit-strip markers (gap 5): drop driver attributes projected solely to
569
- # feed a fan-out from the root, the only parent the emitted refs vocabulary
570
- # allows (nested sourceList is emitter-rejected).
571
- for node in body:
572
- m = node.get("map")
573
- if m is not None and "implicitSourceField" in m["ports"]:
574
- result.pop(m["ports"]["implicitSourceField"], None)
563
+ # Implicit refs-source strip (gap 5): a `refs` fan-out's SOURCE id-list
564
+ # (`tagRefs`) is physically projected onto the root ONLY to feed the fanout
565
+ # `over`; it is a bare-return field the caller never sees. The type-notation
566
+ # annotation encodes exactly this: the root NODE's `outType` carries the
567
+ # source, while the component `outputType.root` OMITS it. So strip precisely
568
+ # the field(s) a root-level `fanout.over` reads that are ABSENT from
569
+ # `outputType.root` (a field the query SELECTS stays in `outputType.root` and
570
+ # is kept). Untyped IR (legacy/hand-built, no annotation) strips nothing —
571
+ # the pre-#347 behavior; nested/non-root fanout over is emitter-rejected.
572
+ output_type = component.get("outputType")
573
+ root_out = (
574
+ output_type.get("obj", {}).get("root")
575
+ if isinstance(output_type, Mapping)
576
+ else None
577
+ )
578
+ kept = (
579
+ set(root_out["obj"].keys())
580
+ if isinstance(root_out, Mapping) and isinstance(root_out.get("obj"), Mapping)
581
+ else None
582
+ )
583
+ if kept is not None:
584
+ for node in body:
585
+ fo = node.get("fanout")
586
+ if fo is None:
587
+ continue
588
+ over = fo.get("over")
589
+ ref = over.get("ref") if isinstance(over, Mapping) else None
590
+ # `over: {ref: ["root", "<source>"]}` — the implicit driver field.
591
+ if isinstance(ref, list) and len(ref) == 2 and ref[0] == "root":
592
+ source = ref[1]
593
+ if source not in kept:
594
+ result.pop(source, None)
575
595
 
576
596
  return result
577
597
 
@@ -735,10 +755,14 @@ def collect_transaction_items(
735
755
 
736
756
  if component_name == "PutItem":
737
757
  plain = _group(ports, "item")
738
- # A raw marker-row write (literalKey) carries its PK/SK verbatim; a
739
- # modeled Put derives PK/SK/GSI from the manifest entity (C4 config).
740
- if not ports.get("literalKey"):
741
- expander._add_key_attributes(entity, plain)
758
+ # A modeled Put's PK/SK/GSI key attributes now ride as DECLARATIVE
759
+ # `key.*` ports (attribute evaluated `concat`/`ref` template the
760
+ # same symbolic derivation reads/updates use, issue #350), so fold
761
+ # them into the written item directly. NO runtime manifest key
762
+ # derivation. A raw marker-row write (literalKey) carries its PK/SK
763
+ # verbatim in `item` and emits no `key.*` group, so the fold is a
764
+ # no-op for it.
765
+ plain.update(_group(ports, "key"))
742
766
  serialized = {k: rt._serializer.serialize(v) for k, v in plain.items()}
743
767
  put: Dict[str, Any] = {"TableName": table, "Item": serialized}
744
768
  expander._apply_condition(cond_spec, put, cond_params, None)
@@ -1951,7 +1951,12 @@ class GraphDDBRuntime:
1951
1951
  field: self._serializer.serialize(resolve_value_template(tmpl, params))
1952
1952
  for field, tmpl in spec.get("item", {}).items()
1953
1953
  }
1954
- self._add_key_attributes(spec["entity"], item, params)
1954
+ # A put's PK/SK/GSI key attributes ride as DECLARATIVE key
1955
+ # templates on `keyCondition` (the same symbolic derivation
1956
+ # read/update/delete use, issue #350); resolve + fold — NO runtime
1957
+ # manifest key derivation.
1958
+ for attr, tmpl in spec.get("keyCondition", {}).items():
1959
+ item[attr] = self._serializer.serialize(resolve_template(tmpl, params))
1955
1960
  requests.append({"PutRequest": {"Item": item}})
1956
1961
  else: # DeleteItem
1957
1962
  dynamo_key = {
@@ -2269,7 +2274,11 @@ class GraphDDBRuntime:
2269
2274
  field: self._serializer.serialize(resolve_value_template(tmpl, params))
2270
2275
  for field, tmpl in item_tmpl.items()
2271
2276
  }
2272
- self._add_key_attributes(spec["entity"], item, params)
2277
+ # A put's PK/SK/GSI key attributes ride as DECLARATIVE key templates on
2278
+ # `keyCondition` (the same symbolic derivation read/update/delete use,
2279
+ # issue #350); resolve + fold — NO runtime manifest key derivation.
2280
+ for attr, tmpl in spec.get("keyCondition", {}).items():
2281
+ item[attr] = self._serializer.serialize(resolve_template(tmpl, params))
2273
2282
 
2274
2283
  request: Dict[str, Any] = {
2275
2284
  "TableName": self._physical_table(spec["tableName"]),
@@ -2549,35 +2558,6 @@ class GraphDDBRuntime:
2549
2558
  values[alias] = self._serializer.serialize(resolved)
2550
2559
  request["ConditionExpression"] = condition["expression"]
2551
2560
 
2552
- def _add_key_attributes(
2553
- self, entity_name: str, item: Dict[str, Any], params: Mapping[str, Any]
2554
- ) -> None:
2555
- entity_meta = self._entities.get(entity_name, {})
2556
- plain = {
2557
- field: self._deserializer.deserialize(av) for field, av in item.items()
2558
- }
2559
- key = entity_meta.get("key")
2560
- if key:
2561
- # pkTemplate is self-contained (segment key model #51); no prefix.
2562
- pk = self._fill(key.get("pkTemplate") or "", plain)
2563
- item["PK"] = self._serializer.serialize(pk)
2564
- sk_tmpl = key.get("skTemplate")
2565
- if sk_tmpl is not None:
2566
- item["SK"] = self._serializer.serialize(self._fill(sk_tmpl, plain))
2567
-
2568
- for gsi in entity_meta.get("gsis", []):
2569
- index = gsi["indexName"]
2570
- pk_tmpl = gsi.get("pkTemplate")
2571
- sk_tmpl = gsi.get("skTemplate")
2572
- if pk_tmpl is not None and self._fillable(pk_tmpl, plain):
2573
- item[f"{index}PK"] = self._serializer.serialize(
2574
- self._fill(pk_tmpl, plain)
2575
- )
2576
- if sk_tmpl is not None and self._fillable(sk_tmpl, plain):
2577
- item[f"{index}SK"] = self._serializer.serialize(
2578
- self._fill(sk_tmpl, plain)
2579
- )
2580
-
2581
2561
  @staticmethod
2582
2562
  def _fillable(template: str, values: Mapping[str, Any]) -> bool:
2583
2563
  return all(
@@ -145,18 +145,6 @@ def _resolve_condition_tree(
145
145
  return out
146
146
 
147
147
 
148
- def _fill_key(template: str, item: Mapping[str, Any]) -> str:
149
- def repl(match: "re.Match[str]") -> str:
150
- name = match.group(0)[1:-1]
151
- return str(item.get(name, ""))
152
-
153
- return _PLACEHOLDER_RE.sub(repl, template)
154
-
155
-
156
- def _key_fillable(template: str, item: Mapping[str, Any]) -> bool:
157
- return all(m.group(0)[1:-1] in item for m in _PLACEHOLDER_RE.finditer(template))
158
-
159
-
160
148
  # ── SCP guard evaluation seam (issue #266, Phase 3 S6) ────────────────────────
161
149
  #
162
150
  # The branch-for-branch port of the TS ``evaluateGuard`` seam
@@ -450,24 +438,6 @@ class TransactionExpander:
450
438
  raise KeyError(f"unknown entity '{name}' in manifest")
451
439
  return entity
452
440
 
453
- def _add_key_attributes(self, entity_name: str, plain: Dict[str, Any]) -> None:
454
- entity = self._entity(entity_name)
455
- key = entity.get("key")
456
- if key:
457
- # pkTemplate is self-contained (segment key model #51); no prefix.
458
- plain["PK"] = _fill_key(key.get("pkTemplate") or "", plain)
459
- sk_tmpl = key.get("skTemplate")
460
- if sk_tmpl is not None:
461
- plain["SK"] = _fill_key(sk_tmpl, plain)
462
- for gsi in entity.get("gsis", []):
463
- index = gsi["indexName"]
464
- pk_tmpl = gsi.get("pkTemplate")
465
- sk_tmpl = gsi.get("skTemplate")
466
- if pk_tmpl is not None and _key_fillable(pk_tmpl, plain):
467
- plain[f"{index}PK"] = _fill_key(pk_tmpl, plain)
468
- if sk_tmpl is not None and _key_fillable(sk_tmpl, plain):
469
- plain[f"{index}SK"] = _fill_key(sk_tmpl, plain)
470
-
471
441
  def _apply_condition(
472
442
  self,
473
443
  condition: Optional[Mapping[str, Any]],
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 1.0.7
3
+ Version: 1.0.9
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
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: boto3>=1.26
9
- Requires-Dist: behavior-contracts==0.7.1
9
+ Requires-Dist: behavior-contracts==0.7.3
10
10
  Provides-Extra: test
11
11
  Requires-Dist: pytest>=7.0; extra == "test"
12
12
 
@@ -1,5 +1,5 @@
1
1
  boto3>=1.26
2
- behavior-contracts==0.7.1
2
+ behavior-contracts==0.7.3
3
3
 
4
4
  [test]
5
5
  pytest>=7.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graphddb-runtime"
7
- version = "1.0.7"
7
+ version = "1.0.9"
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"
@@ -14,7 +14,7 @@ license = { text = "MIT" }
14
14
  # canonicalisation) are delegated to the extracted shared package
15
15
  # ``behavior-contracts`` (import name ``behavior_contracts``). The runtime
16
16
  # imports it unconditionally, so it is a HARD dependency.
17
- dependencies = ["boto3>=1.26", "behavior-contracts==0.7.1"]
17
+ dependencies = ["boto3>=1.26", "behavior-contracts==0.7.3"]
18
18
 
19
19
  [project.optional-dependencies]
20
20
  test = ["pytest>=7.0"]