Download xtask/python_graph.py from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 2.6 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/2d840a0fc49ec00f91cf1f8384861985afa519af/xtask/python_graph.py
- Command line
-
hf download hf://nmarshall/urt-rs@2d840a0fc49ec00f91cf1f8384861985afa519af/xtask/python_graph.py
-
curl -L -o python_graph.py https://huggingface.co/nmarshall/urt-rs/resolve/2d840a0fc49ec00f91cf1f8384861985afa519af/xtask/python_graph.py
2.6 kB
| """Read Python syntax only; Rust owns selection, scheduling and execution. | |
| Protocol is tab-separated UTF-8. Never import repository modules. Dynamic file | |
| and subprocess dependencies must be declared in checks.tsv. | |
| """ | |
| import ast | |
| from pathlib import Path | |
| import sys | |
| def scan(root, extra=()): | |
| files = sorted((root / 'scripts').glob('*.py')) | |
| modules = {p.stem: 'scripts/' + p.name for p in files} | |
| for p in extra: | |
| if p.startswith('scripts/') and p.endswith('.py'): | |
| modules[Path(p).stem] = p | |
| rows = set() | |
| for path in files: | |
| name = 'scripts/' + path.name | |
| rows.add(('node', name)) | |
| tree = ast.parse(path.read_text(), filename=name) | |
| outputs = set() | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Import): | |
| imports = [a.name.split('.')[0] for a in node.names] | |
| elif isinstance(node, ast.ImportFrom): | |
| imports = [(node.module or '').split('.')[0]] | |
| else: | |
| imports = [] | |
| for module in imports: | |
| if module in modules: | |
| rows.add(('edge', modules[module], name)) | |
| if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in ('write_json', 'write_rows', 'write_gzip') and node.args: | |
| dest = node.args[0] | |
| if isinstance(dest, ast.Call) and isinstance(dest.func, ast.Name) and dest.func.id == 'Path' and dest.args and isinstance(dest.args[0], ast.Constant) and isinstance(dest.args[0].value, str): | |
| outputs.add(dest.args[0].value) | |
| rows.add(('output', name, dest.args[0].value)) | |
| else: | |
| rows.add(('dynamic', name)) | |
| if isinstance(node, ast.Call) and ((isinstance(node.func, ast.Name) and node.func.id == '__import__') or (isinstance(node.func, ast.Attribute) and node.func.attr == 'import_module')): | |
| rows.add(('unsafe', name)) | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Constant) and isinstance(node.value, str): | |
| value = node.value | |
| if value.startswith(('docs/', 'assets/', '.cache/')) and '\n' not in value and '\t' not in value and value not in outputs: | |
| # Direct file literals only. Prefixes/formatted paths belong in the registry. | |
| if Path(value).suffix: | |
| rows.add(('edge', value, name)) | |
| return sorted(rows) | |
| if __name__ == '__main__': | |
| for row in scan(Path.cwd(), sys.argv[1:]): | |
| print('\t'.join(row)) | |