py2dag 0.1.13__tar.gz → 0.1.14__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.1.13
3
+ Version: 0.1.14
4
4
  Summary: Convert Python function plans to DAG (JSON, pseudo, optional SVG).
5
5
  License: MIT
6
6
  Author: rvergis
@@ -47,6 +47,20 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
47
47
  settings: Dict[str, Any] = {}
48
48
 
49
49
  returned_var: Optional[str] = None
50
+ # Treat function parameters as pre-defined names
51
+ try:
52
+ for arg in getattr(fn, "args").args: # type: ignore[attr-defined]
53
+ defined.add(arg.arg)
54
+ except Exception:
55
+ pass
56
+
57
+ def _collect_name_deps(node: ast.AST) -> List[str]:
58
+ names: List[str] = []
59
+ for n in ast.walk(node):
60
+ if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Load):
61
+ if n.id not in names:
62
+ names.append(n.id)
63
+ return names
50
64
  # type: ignore[attr-defined]
51
65
  for i, stmt in enumerate(fn.body): # type: ignore[attr-defined]
52
66
  if isinstance(stmt, ast.Assign):
@@ -65,8 +79,32 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
65
79
  op_name = _get_call_name(value.func)
66
80
 
67
81
  deps: List[str] = []
82
+
83
+ def _expand_star_name(varname: str) -> List[str]:
84
+ # Try to expand a previously packed list/tuple variable into its element deps
85
+ for prev in reversed(ops):
86
+ if prev.get("id") == varname:
87
+ if prev.get("op") in {"PACK.list", "PACK.tuple"}:
88
+ return list(prev.get("deps", []))
89
+ break
90
+ return [varname]
68
91
  for arg in value.args:
69
- if isinstance(arg, ast.Name):
92
+ if isinstance(arg, ast.Starred):
93
+ star_val = arg.value
94
+ if isinstance(star_val, ast.Name):
95
+ if star_val.id not in defined:
96
+ raise DSLParseError(f"Undefined dependency: {star_val.id}")
97
+ deps.extend(_expand_star_name(star_val.id))
98
+ elif isinstance(star_val, (ast.List, ast.Tuple)):
99
+ for elt in star_val.elts:
100
+ if not isinstance(elt, ast.Name):
101
+ raise DSLParseError("Starred list/tuple elements must be names")
102
+ if elt.id not in defined:
103
+ raise DSLParseError(f"Undefined dependency: {elt.id}")
104
+ deps.append(elt.id)
105
+ else:
106
+ raise DSLParseError("*args must be a name or list/tuple of names")
107
+ elif isinstance(arg, ast.Name):
70
108
  if arg.id not in defined:
71
109
  raise DSLParseError(f"Undefined dependency: {arg.id}")
72
110
  deps.append(arg.id)
@@ -83,15 +121,28 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
83
121
  kwargs: Dict[str, Any] = {}
84
122
  for kw in value.keywords:
85
123
  if kw.arg is None:
86
- raise DSLParseError("**kwargs are not allowed")
87
- # Support variable-name keyword args as dependencies; literals remain in args
88
- if isinstance(kw.value, ast.Name):
89
- name = kw.value.id
90
- if name not in defined:
91
- raise DSLParseError(f"Undefined dependency: {name}")
92
- deps.append(name)
124
+ # **kwargs support: allow dict literal merge, or variable name as dep
125
+ v = kw.value
126
+ if isinstance(v, ast.Dict):
127
+ # Merge literal kwargs
128
+ lit = _literal(v)
129
+ for k, val in lit.items():
130
+ kwargs[str(k)] = val
131
+ elif isinstance(v, ast.Name):
132
+ if v.id not in defined:
133
+ raise DSLParseError(f"Undefined dependency: {v.id}")
134
+ deps.append(v.id)
135
+ else:
136
+ raise DSLParseError("**kwargs must be a dict literal or a variable name")
93
137
  else:
94
- kwargs[kw.arg] = _literal(kw.value)
138
+ # Support variable-name keyword args as dependencies; literals remain in args
139
+ if isinstance(kw.value, ast.Name):
140
+ name = kw.value.id
141
+ if name not in defined:
142
+ raise DSLParseError(f"Undefined dependency: {name}")
143
+ deps.append(name)
144
+ else:
145
+ kwargs[kw.arg] = _literal(kw.value)
95
146
 
96
147
  ops.append({"id": var_name, "op": op_name, "deps": deps, "args": kwargs})
97
148
  elif isinstance(value, ast.JoinedStr):
@@ -117,13 +168,52 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
117
168
  "args": {"template": template},
118
169
  })
119
170
  elif isinstance(value, (ast.Constant, ast.List, ast.Tuple, ast.Dict)):
120
- # Allow assigning JSON-serialisable literals directly
121
- lit = _literal(value)
171
+ # Allow assigning literals; also support packing lists/tuples of names
172
+ try:
173
+ lit = _literal(value)
174
+ ops.append({
175
+ "id": var_name,
176
+ "op": "CONST.value",
177
+ "deps": [],
178
+ "args": {"value": lit},
179
+ })
180
+ except DSLParseError:
181
+ if isinstance(value, (ast.List, ast.Tuple)):
182
+ elts = value.elts
183
+ names: List[str] = []
184
+ for elt in elts:
185
+ if not isinstance(elt, ast.Name):
186
+ raise DSLParseError("Only names allowed in non-literal list/tuple assignment")
187
+ if elt.id not in defined:
188
+ raise DSLParseError(f"Undefined dependency: {elt.id}")
189
+ names.append(elt.id)
190
+ kind = "list" if isinstance(value, ast.List) else "tuple"
191
+ ops.append({
192
+ "id": var_name,
193
+ "op": f"PACK.{kind}",
194
+ "deps": names,
195
+ "args": {},
196
+ })
197
+ else:
198
+ raise
199
+ elif isinstance(value, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
200
+ # Basic comprehension support: collect name deps and emit a generic comp op
201
+ name_deps = [n for n in _collect_name_deps(value) if n in defined]
202
+ # Ensure no undefined names used
203
+ for n in name_deps:
204
+ if n not in defined:
205
+ raise DSLParseError(f"Undefined dependency: {n}")
206
+ kind = (
207
+ "listcomp" if isinstance(value, ast.ListComp) else
208
+ "setcomp" if isinstance(value, ast.SetComp) else
209
+ "dictcomp" if isinstance(value, ast.DictComp) else
210
+ "genexpr"
211
+ )
122
212
  ops.append({
123
213
  "id": var_name,
124
- "op": "CONST.value",
125
- "deps": [],
126
- "args": {"value": lit},
214
+ "op": f"COMP.{kind}",
215
+ "deps": name_deps,
216
+ "args": {},
127
217
  })
128
218
  else:
129
219
  raise DSLParseError("Right hand side must be a call or f-string")
@@ -186,7 +276,7 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
186
276
  returned_var = const_id
187
277
  else:
188
278
  raise DSLParseError("return must return a variable name or literal")
189
- elif isinstance(stmt, (ast.For, ast.AsyncFor, ast.While, ast.If)):
279
+ elif isinstance(stmt, (ast.For, ast.AsyncFor, ast.While, ast.If, ast.Match)):
190
280
  # Ignore control flow blocks; only top-level linear statements are modeled
191
281
  continue
192
282
  elif isinstance(stmt, (ast.Pass,)):
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "py2dag"
7
- version = "0.1.13"
7
+ version = "0.1.14"
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
File without changes
File without changes