py2dag 0.1.13__tar.gz → 0.1.15__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.15}/PKG-INFO +1 -1
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/parser.py +110 -15
- {py2dag-0.1.13 → py2dag-0.1.15}/pyproject.toml +1 -1
- {py2dag-0.1.13 → py2dag-0.1.15}/LICENSE +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/README.md +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/__init__.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/cli.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/export_dagre.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/export_svg.py +0 -0
- {py2dag-0.1.13 → py2dag-0.1.15}/py2dag/pseudo.py +0 -0
@@ -47,6 +47,25 @@ 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
|
+
# Enforce no-args top-level function signature
|
51
|
+
try:
|
52
|
+
fargs = getattr(fn, "args") # type: ignore[attr-defined]
|
53
|
+
has_params = bool(
|
54
|
+
getattr(fargs, "posonlyargs", []) or fargs.args or fargs.vararg or fargs.kwonlyargs or fargs.kwarg
|
55
|
+
)
|
56
|
+
if has_params:
|
57
|
+
raise DSLParseError("Top-level function must not accept parameters")
|
58
|
+
except AttributeError:
|
59
|
+
# If args not present, ignore
|
60
|
+
pass
|
61
|
+
|
62
|
+
def _collect_name_deps(node: ast.AST) -> List[str]:
|
63
|
+
names: List[str] = []
|
64
|
+
for n in ast.walk(node):
|
65
|
+
if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Load):
|
66
|
+
if n.id not in names:
|
67
|
+
names.append(n.id)
|
68
|
+
return names
|
50
69
|
# type: ignore[attr-defined]
|
51
70
|
for i, stmt in enumerate(fn.body): # type: ignore[attr-defined]
|
52
71
|
if isinstance(stmt, ast.Assign):
|
@@ -65,8 +84,32 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
65
84
|
op_name = _get_call_name(value.func)
|
66
85
|
|
67
86
|
deps: List[str] = []
|
87
|
+
|
88
|
+
def _expand_star_name(varname: str) -> List[str]:
|
89
|
+
# Try to expand a previously packed list/tuple variable into its element deps
|
90
|
+
for prev in reversed(ops):
|
91
|
+
if prev.get("id") == varname:
|
92
|
+
if prev.get("op") in {"PACK.list", "PACK.tuple"}:
|
93
|
+
return list(prev.get("deps", []))
|
94
|
+
break
|
95
|
+
return [varname]
|
68
96
|
for arg in value.args:
|
69
|
-
if isinstance(arg, ast.
|
97
|
+
if isinstance(arg, ast.Starred):
|
98
|
+
star_val = arg.value
|
99
|
+
if isinstance(star_val, ast.Name):
|
100
|
+
if star_val.id not in defined:
|
101
|
+
raise DSLParseError(f"Undefined dependency: {star_val.id}")
|
102
|
+
deps.extend(_expand_star_name(star_val.id))
|
103
|
+
elif isinstance(star_val, (ast.List, ast.Tuple)):
|
104
|
+
for elt in star_val.elts:
|
105
|
+
if not isinstance(elt, ast.Name):
|
106
|
+
raise DSLParseError("Starred list/tuple elements must be names")
|
107
|
+
if elt.id not in defined:
|
108
|
+
raise DSLParseError(f"Undefined dependency: {elt.id}")
|
109
|
+
deps.append(elt.id)
|
110
|
+
else:
|
111
|
+
raise DSLParseError("*args must be a name or list/tuple of names")
|
112
|
+
elif isinstance(arg, ast.Name):
|
70
113
|
if arg.id not in defined:
|
71
114
|
raise DSLParseError(f"Undefined dependency: {arg.id}")
|
72
115
|
deps.append(arg.id)
|
@@ -83,15 +126,28 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
83
126
|
kwargs: Dict[str, Any] = {}
|
84
127
|
for kw in value.keywords:
|
85
128
|
if kw.arg is None:
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
129
|
+
# **kwargs support: allow dict literal merge, or variable name as dep
|
130
|
+
v = kw.value
|
131
|
+
if isinstance(v, ast.Dict):
|
132
|
+
# Merge literal kwargs
|
133
|
+
lit = _literal(v)
|
134
|
+
for k, val in lit.items():
|
135
|
+
kwargs[str(k)] = val
|
136
|
+
elif isinstance(v, ast.Name):
|
137
|
+
if v.id not in defined:
|
138
|
+
raise DSLParseError(f"Undefined dependency: {v.id}")
|
139
|
+
deps.append(v.id)
|
140
|
+
else:
|
141
|
+
raise DSLParseError("**kwargs must be a dict literal or a variable name")
|
93
142
|
else:
|
94
|
-
|
143
|
+
# Support variable-name keyword args as dependencies; literals remain in args
|
144
|
+
if isinstance(kw.value, ast.Name):
|
145
|
+
name = kw.value.id
|
146
|
+
if name not in defined:
|
147
|
+
raise DSLParseError(f"Undefined dependency: {name}")
|
148
|
+
deps.append(name)
|
149
|
+
else:
|
150
|
+
kwargs[kw.arg] = _literal(kw.value)
|
95
151
|
|
96
152
|
ops.append({"id": var_name, "op": op_name, "deps": deps, "args": kwargs})
|
97
153
|
elif isinstance(value, ast.JoinedStr):
|
@@ -117,13 +173,52 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
117
173
|
"args": {"template": template},
|
118
174
|
})
|
119
175
|
elif isinstance(value, (ast.Constant, ast.List, ast.Tuple, ast.Dict)):
|
120
|
-
# Allow assigning
|
121
|
-
|
176
|
+
# Allow assigning literals; also support packing lists/tuples of names
|
177
|
+
try:
|
178
|
+
lit = _literal(value)
|
179
|
+
ops.append({
|
180
|
+
"id": var_name,
|
181
|
+
"op": "CONST.value",
|
182
|
+
"deps": [],
|
183
|
+
"args": {"value": lit},
|
184
|
+
})
|
185
|
+
except DSLParseError:
|
186
|
+
if isinstance(value, (ast.List, ast.Tuple)):
|
187
|
+
elts = value.elts
|
188
|
+
names: List[str] = []
|
189
|
+
for elt in elts:
|
190
|
+
if not isinstance(elt, ast.Name):
|
191
|
+
raise DSLParseError("Only names allowed in non-literal list/tuple assignment")
|
192
|
+
if elt.id not in defined:
|
193
|
+
raise DSLParseError(f"Undefined dependency: {elt.id}")
|
194
|
+
names.append(elt.id)
|
195
|
+
kind = "list" if isinstance(value, ast.List) else "tuple"
|
196
|
+
ops.append({
|
197
|
+
"id": var_name,
|
198
|
+
"op": f"PACK.{kind}",
|
199
|
+
"deps": names,
|
200
|
+
"args": {},
|
201
|
+
})
|
202
|
+
else:
|
203
|
+
raise
|
204
|
+
elif isinstance(value, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
|
205
|
+
# Basic comprehension support: collect name deps and emit a generic comp op
|
206
|
+
name_deps = [n for n in _collect_name_deps(value) if n in defined]
|
207
|
+
# Ensure no undefined names used
|
208
|
+
for n in name_deps:
|
209
|
+
if n not in defined:
|
210
|
+
raise DSLParseError(f"Undefined dependency: {n}")
|
211
|
+
kind = (
|
212
|
+
"listcomp" if isinstance(value, ast.ListComp) else
|
213
|
+
"setcomp" if isinstance(value, ast.SetComp) else
|
214
|
+
"dictcomp" if isinstance(value, ast.DictComp) else
|
215
|
+
"genexpr"
|
216
|
+
)
|
122
217
|
ops.append({
|
123
218
|
"id": var_name,
|
124
|
-
"op": "
|
125
|
-
"deps":
|
126
|
-
"args": {
|
219
|
+
"op": f"COMP.{kind}",
|
220
|
+
"deps": name_deps,
|
221
|
+
"args": {},
|
127
222
|
})
|
128
223
|
else:
|
129
224
|
raise DSLParseError("Right hand side must be a call or f-string")
|
@@ -186,7 +281,7 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
186
281
|
returned_var = const_id
|
187
282
|
else:
|
188
283
|
raise DSLParseError("return must return a variable name or literal")
|
189
|
-
elif isinstance(stmt, (ast.For, ast.AsyncFor, ast.While, ast.If)):
|
284
|
+
elif isinstance(stmt, (ast.For, ast.AsyncFor, ast.While, ast.If, ast.Match)):
|
190
285
|
# Ignore control flow blocks; only top-level linear statements are modeled
|
191
286
|
continue
|
192
287
|
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
|