-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvalidate_dag.py
More file actions
135 lines (108 loc) · 4.23 KB
/
Copy pathvalidate_dag.py
File metadata and controls
135 lines (108 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""
Airflow DAG Validation Script
==============================
This script validates the Airflow DAG structure without requiring Airflow to run.
Useful for Windows environments where Airflow requires WSL.
Validates:
- DAG file syntax
- DAG configuration
- Task dependencies
- Task commands
"""
import sys
import ast
import json
from pathlib import Path
def validate_dag_file(dag_path):
"""Validate DAG Python file syntax and structure."""
results = {
"file_exists": False,
"syntax_valid": False,
"dag_found": False,
"tasks_found": [],
"dependencies": [],
"errors": []
}
try:
dag_file = Path(dag_path)
# Check file exists
if not dag_file.exists():
results["errors"].append(f"DAG file not found: {dag_path}")
return results
results["file_exists"] = True
# Read and parse file
with open(dag_file, 'r', encoding='utf-8') as f:
content = f.read()
# Check syntax
try:
tree = ast.parse(content)
results["syntax_valid"] = True
except SyntaxError as e:
results["errors"].append(f"Syntax error: {e}")
return results
# Analyze AST for DAG and task definitions
for node in ast.walk(tree):
# Look for DAG instantiation
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id == 'DAG':
results["dag_found"] = True
# Look for task assignments (BashOperator, PythonOperator, etc.)
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name):
task_name = target.id
if task_name.endswith('_task') or task_name.endswith('_operator'):
results["tasks_found"].append(task_name)
# Check for specific task IDs in the file content
task_ids = []
for line in content.split('\n'):
if 'task_id=' in line:
# Extract task_id value
start = line.find("task_id='") or line.find('task_id="')
if start != -1:
start += 9
end = line.find("'", start) if line.find("'", start) != -1 else line.find('"', start)
if end != -1:
task_id = line[start:end]
task_ids.append(task_id)
results["task_ids"] = task_ids
# Look for dependencies (>>)
if '>>' in content:
results["dependencies"].append("Task dependencies found using >> operator")
print(f"✓ DAG file validation successful")
print(f" - File: {dag_path}")
print(f" - Syntax: Valid")
print(f" - DAG found: {results['dag_found']}")
print(f" - Task IDs found: {task_ids}")
print(f" - Dependencies: {'Yes' if results['dependencies'] else 'No'}")
except Exception as e:
results["errors"].append(f"Unexpected error: {str(e)}")
return results
if __name__ == "__main__":
# Validate both DAG locations
dag_paths = [
"dags/telco_churn_dag.py",
"airflow_home/dags/telco_churn_dag.py"
]
all_results = {}
for dag_path in dag_paths:
print(f"\n{'='*60}")
print(f"Validating: {dag_path}")
print(f"{'='*60}")
results = validate_dag_file(dag_path)
all_results[dag_path] = results
if results["errors"]:
print(f"\n✗ Validation errors:")
for error in results["errors"]:
print(f" - {error}")
# Save results
output_file = Path("reports/dag_validation.json")
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w') as f:
json.dump(all_results, f, indent=2)
print(f"\n{'='*60}")
print(f"✓ Validation results saved to: {output_file}")
print(f"{'='*60}")
# Exit with appropriate code
has_errors = any(r.get("errors") for r in all_results.values())
sys.exit(1 if has_errors else 0)