py2dag 0.2.1__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: py2dag
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Convert Python function plans to DAG (JSON, pseudo, optional SVG).
5
5
  License: MIT
6
6
  Author: rvergis
@@ -73,10 +73,21 @@ HTML_TEMPLATE = """<!doctype html>
73
73
  g.setEdge(out.from, outId);
74
74
  });
75
75
 
76
- // Add dependency edges between ops
76
+ // Build index for source op lookup
77
+ const opById = {};
78
+ (plan.ops || []).forEach(op => { opById[op.id] = op; });
79
+
80
+ // Add dependency edges between ops with labels
77
81
  (plan.ops || []).forEach(op => {
78
82
  (op.deps || []).forEach(dep => {
79
- g.setEdge(dep, op.id);
83
+ const src = opById[dep];
84
+ let edgeLabel = dep; // default to SSA id
85
+ if (src && src.op === 'COND.eval') {
86
+ edgeLabel = 'cond';
87
+ } else if (src && src.op === 'ITER.eval') {
88
+ edgeLabel = (src.args && src.args.target) ? src.args.target : 'iter';
89
+ }
90
+ g.setEdge(dep, op.id, { label: edgeLabel });
80
91
  });
81
92
  });
82
93
 
@@ -22,10 +22,27 @@ def export(plan: Dict[str, Any], filename: str = "plan.svg") -> str:
22
22
  raise RuntimeError("Python package 'graphviz' is required for SVG export")
23
23
 
24
24
  graph = Digraph(format="svg")
25
- for op in plan.get("ops", []):
25
+
26
+ # Index ops for edge label decisions
27
+ ops = list(plan.get("ops", []))
28
+ op_by_id = {op["id"]: op for op in ops}
29
+
30
+ # Nodes
31
+ for op in ops:
26
32
  graph.node(op["id"], label=op["op"])
33
+
34
+ # Dependency edges with labels showing data/control
35
+ for op in ops:
27
36
  for dep in op.get("deps", []):
28
- graph.edge(dep, op["id"])
37
+ src = op_by_id.get(dep)
38
+ label = dep # default to SSA id
39
+ if src is not None:
40
+ if src.get("op") == "COND.eval":
41
+ label = "cond"
42
+ elif src.get("op") == "ITER.eval":
43
+ args = src.get("args", {}) or {}
44
+ label = str(args.get("target") or "iter")
45
+ graph.edge(dep, op["id"], label=label)
29
46
  for out in plan.get("outputs", []):
30
47
  out_id = f"out:{out['as']}"
31
48
  graph.node(out_id, label=out['as'], shape="note")
@@ -252,11 +252,14 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
252
252
  ops.append({"id": ssa, "op": "COND.eval", "deps": deps, "args": {"expr": expr, "kind": kind}})
253
253
  return ssa
254
254
 
255
- def _emit_iter(node: ast.AST) -> str:
255
+ def _emit_iter(node: ast.AST, target_label: Optional[str] = None) -> str:
256
256
  expr = _stringify(node)
257
257
  deps = [_ssa_get(n) for n in _collect_value_deps(node)]
258
258
  ssa = _ssa_new("iter")
259
- ops.append({"id": ssa, "op": "ITER.eval", "deps": deps, "args": {"expr": expr, "kind": "for"}})
259
+ args = {"expr": expr, "kind": "for"}
260
+ if target_label:
261
+ args["target"] = target_label
262
+ ops.append({"id": ssa, "op": "ITER.eval", "deps": deps, "args": args})
260
263
  return ssa
261
264
 
262
265
  def _parse_stmt(stmt: ast.stmt) -> Optional[str]:
@@ -383,7 +386,19 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
383
386
  return None
384
387
  elif isinstance(stmt, (ast.For, ast.AsyncFor)):
385
388
  # ITER over iterable
386
- iter_id = _emit_iter(stmt.iter)
389
+ # Determine loop target label if simple
390
+ t = stmt.target
391
+ t_label: Optional[str] = None
392
+ if isinstance(t, ast.Name):
393
+ t_label = t.id
394
+ elif isinstance(t, ast.Tuple) and all(isinstance(e, ast.Name) for e in t.elts):
395
+ t_label = ",".join(e.id for e in t.elts) # type: ignore[attr-defined]
396
+ else:
397
+ try:
398
+ t_label = ast.unparse(t) # type: ignore[attr-defined]
399
+ except Exception:
400
+ t_label = None
401
+ iter_id = _emit_iter(stmt.iter, target_label=t_label)
387
402
  # Save pre-loop state
388
403
  pre_versions = dict(versions)
389
404
  pre_latest = dict(latest)
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "py2dag"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Convert Python function plans to DAG (JSON, pseudo, optional SVG)."
9
9
  authors = ["rvergis"]
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes