graphddb-runtime 1.0.7__tar.gz → 1.0.8__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.8}/PKG-INFO +2 -2
  2. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/behavior_exec.py +62 -42
  3. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime.egg-info/PKG-INFO +2 -2
  4. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime.egg-info/requires.txt +1 -1
  5. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/pyproject.toml +2 -2
  6. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/README.md +0 -0
  7. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/__init__.py +0 -0
  8. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/async_runtime.py +0 -0
  9. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/batch.py +0 -0
  10. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/concurrency.py +0 -0
  11. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/cursor.py +0 -0
  12. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/errors.py +0 -0
  13. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/filters.py +0 -0
  14. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/handler_registry.py +0 -0
  15. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/hydration.py +0 -0
  16. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/limits.py +0 -0
  17. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/middleware.py +0 -0
  18. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/per_key_cursor.py +0 -0
  19. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/runtime.py +0 -0
  20. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/templates.py +0 -0
  21. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime/transactions.py +0 -0
  22. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime.egg-info/SOURCES.txt +0 -0
  23. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime.egg-info/dependency_links.txt +0 -0
  24. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/graphddb_runtime.egg-info/top_level.txt +0 -0
  25. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/setup.cfg +0 -0
  26. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_atomic_add_command.py +0 -0
  27. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_concurrency.py +0 -0
  28. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_contract_runtime.py +0 -0
  29. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_crosslang_python_cells.py +0 -0
  30. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_description.py +0 -0
  31. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_description_followups.py +0 -0
  32. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_description_jsdoc.py +0 -0
  33. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_error_policy.py +0 -0
  34. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_generated_module.py +0 -0
  35. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_guard_eval.py +0 -0
  36. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_handler_registry.py +0 -0
  37. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration.py +0 -0
  38. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_batch_write.py +0 -0
  39. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_binding_contract.py +0 -0
  40. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_command.py +0 -0
  41. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_compose.py +0 -0
  42. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_contract.py +0 -0
  43. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_edge_derive.py +0 -0
  44. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_edge_write.py +0 -0
  45. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_events.py +0 -0
  46. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_maintain.py +0 -0
  47. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_middleware.py +0 -0
  48. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_referential.py +0 -0
  49. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_relations.py +0 -0
  50. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_integration_unique.py +0 -0
  51. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_maintain.py +0 -0
  52. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_middleware.py +0 -0
  53. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_relations.py +0 -0
  54. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_spec_version_guard.py +0 -0
  55. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/tests/test_ttl.py +0 -0
  56. {graphddb_runtime-1.0.7 → graphddb_runtime-1.0.8}/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.8
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
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphddb-runtime
3
- Version: 1.0.7
3
+ Version: 1.0.8
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.8"
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"]