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.
- {py2dag-0.1.13 → py2dag-0.1.14}/PKG-INFO +1 -1
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/parser.py +105 -15
- {py2dag-0.1.13 → py2dag-0.1.14}/pyproject.toml +1 -1
- {py2dag-0.1.13 → py2dag-0.1.14}/LICENSE +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/README.md +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/__init__.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/cli.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/export_dagre.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/export_svg.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.14}/py2dag/pseudo.py +0 -0
@@ -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.
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
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
|
121
|
-
|
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": "
|
125
|
-
"deps":
|
126
|
-
"args": {
|
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,)):
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|