Coverage for openhcs/validation/validate.py: 0.0%

69 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1#!/usr/bin/env python 

2""" 

3Command-line tool for running AST-based validation on openhcs codebase. 

4 

5This tool validates Python files for compliance with openhcs's architectural 

6principles using static AST-based analysis. 

7""" 

8 

9import argparse 

10import os 

11import sys 

12from pathlib import Path 

13from typing import List, Optional, Set 

14 

15from openhcs.validation.ast_validator import ValidationViolation, validate_file 

16 

17 

18def find_python_files(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[Path]: 

19 """ 

20 Find all Python files in a directory recursively using breadth-first traversal. 

21 

22 Args: 

23 directory: Directory to search. 

24 exclude_dirs: Set of directory names to exclude. 

25 

26 Returns: 

27 List of Python file paths sorted by depth (shallower first). 

28 """ 

29 from collections import deque 

30 

31 if exclude_dirs is None: 

32 exclude_dirs = set() 

33 

34 python_files = [] 

35 # Use deque for breadth-first traversal 

36 dirs_to_search = deque([(directory, 0)]) # (path, depth) 

37 

38 while dirs_to_search: 

39 current_dir, depth = dirs_to_search.popleft() 

40 

41 try: 

42 for entry in current_dir.iterdir(): 

43 if entry.is_file() and entry.suffix == '.py': 

44 python_files.append((entry, depth)) 

45 elif entry.is_dir() and entry.name not in exclude_dirs: 

46 # Add subdirectory to queue for later processing 

47 dirs_to_search.append((entry, depth + 1)) 

48 except (PermissionError, OSError): 

49 # Skip directories we can't read 

50 continue 

51 

52 # Sort by depth first, then by path for consistent ordering 

53 python_files.sort(key=lambda x: (x[1], str(x[0]))) 

54 

55 # Return just the paths 

56 return [file_path for file_path, _ in python_files] 

57 

58 

59def validate_directory(directory: Path, exclude_dirs: Optional[Set[str]] = None) -> List[ValidationViolation]: 

60 """ 

61 Validate all Python files in a directory. 

62  

63 Args: 

64 directory: Directory to validate. 

65 exclude_dirs: Set of directory names to exclude. 

66  

67 Returns: 

68 List of validation violations. 

69 """ 

70 python_files = find_python_files(directory, exclude_dirs) 

71 violations = [] 

72 

73 for file_path in python_files: 

74 file_violations = validate_file(str(file_path)) 

75 violations.extend(file_violations) 

76 

77 return violations 

78 

79 

80def main(): 

81 """Main entry point for the validation tool.""" 

82 parser = argparse.ArgumentParser( 

83 description='AST-based validation for openhcs codebase' 

84 ) 

85 parser.add_argument( 

86 'paths', 

87 nargs='+', 

88 type=str, 

89 help='Files or directories to validate' 

90 ) 

91 parser.add_argument( 

92 '--exclude', 

93 nargs='+', 

94 default=['__pycache__', '.git', 'venv', 'env', '.venv', '.env'], 

95 help='Directories to exclude from validation' 

96 ) 

97 parser.add_argument( 

98 '--output', 

99 type=str, 

100 help='Output file for validation results (default: stdout)' 

101 ) 

102 parser.add_argument( 

103 '--fail-on-error', 

104 action='store_true', 

105 help='Exit with non-zero status if violations are found' 

106 ) 

107 

108 args = parser.parse_args() 

109 

110 # Convert paths to Path objects 

111 paths = [Path(p) for p in args.paths] 

112 exclude_dirs = set(args.exclude) 

113 

114 # Collect all violations 

115 all_violations = [] 

116 

117 for path in paths: 

118 if path.is_file() and path.suffix == '.py': 

119 file_violations = validate_file(str(path)) 

120 all_violations.extend(file_violations) 

121 elif path.is_dir(): 

122 dir_violations = validate_directory(path, exclude_dirs) 

123 all_violations.extend(dir_violations) 

124 else: 

125 print(f"Warning: {path} is not a Python file or directory", file=sys.stderr) 

126 

127 # Group violations by file 

128 violations_by_file = {} 

129 for violation in all_violations: 

130 if violation.file_path not in violations_by_file: 

131 violations_by_file[violation.file_path] = [] 

132 violations_by_file[violation.file_path].append(violation) 

133 

134 # Sort violations by file and line number 

135 for file_path in violations_by_file: 

136 violations_by_file[file_path].sort(key=lambda v: v.line_number) 

137 

138 # Output violations 

139 output_file = open(args.output, 'w') if args.output else sys.stdout 

140 

141 try: 

142 if all_violations: 

143 print(f"Found {len(all_violations)} validation violations:", file=output_file) 

144 

145 for file_path, violations in sorted(violations_by_file.items()): 

146 print(f"\n{file_path}:", file=output_file) 

147 

148 for violation in violations: 

149 print(f" Line {violation.line_number}: {violation.violation_type} - {violation.message}", 

150 file=output_file) 

151 

152 if args.fail_on_error: 

153 sys.exit(1) 

154 else: 

155 print("No validation violations found.", file=output_file) 

156 finally: 

157 if args.output: 

158 output_file.close() 

159 

160 

161if __name__ == '__main__': 

162 main()