Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Project Structure Validation Script | |
| This script validates the final project structure after cleanup, | |
| checking for compliance with requirements and best practices. | |
| """ | |
| import os | |
| import json | |
| from pathlib import Path | |
| from datetime import datetime | |
| from typing import Dict, List, Tuple, Any | |
| class ProjectStructureValidator: | |
| def __init__(self, root_path: str = "."): | |
| self.root_path = Path(root_path) | |
| self.validation_results = {} | |
| self.issues = [] | |
| self.recommendations = [] | |
| def count_root_items(self) -> Tuple[int, List[str]]: | |
| """Count items in root directory, excluding hidden files and directories.""" | |
| items = [] | |
| hidden_items = [] | |
| for item in self.root_path.iterdir(): | |
| if item.name.startswith('.'): | |
| hidden_items.append(item.name) | |
| else: | |
| items.append(item.name) | |
| return len(items), items, hidden_items | |
| def validate_directory_organization(self) -> Dict[str, Any]: | |
| """Validate logical directory organization.""" | |
| expected_structure = { | |
| 'app.py': 'file', # Required at root for HF Spaces | |
| 'requirements.txt': 'file', # Required at root for HF Spaces | |
| 'README.md': 'file', # Project documentation | |
| 'analytics': 'directory', # Core analytics module | |
| 'tests': 'directory', # All test files | |
| 'docs': 'directory', # Documentation | |
| 'scripts': 'directory', # Utility scripts | |
| 'archive': 'directory', # Archived files | |
| } | |
| organization_results = { | |
| 'expected_present': {}, | |
| 'unexpected_items': [], | |
| 'missing_items': [] | |
| } | |
| # Check for expected items | |
| for item_name, item_type in expected_structure.items(): | |
| item_path = self.root_path / item_name | |
| if item_path.exists(): | |
| actual_type = 'directory' if item_path.is_dir() else 'file' | |
| organization_results['expected_present'][item_name] = { | |
| 'expected_type': item_type, | |
| 'actual_type': actual_type, | |
| 'correct': actual_type == item_type | |
| } | |
| else: | |
| organization_results['missing_items'].append(item_name) | |
| # Check for unexpected items in root | |
| _, root_items, _ = self.count_root_items() | |
| for item in root_items: | |
| if item not in expected_structure: | |
| organization_results['unexpected_items'].append(item) | |
| return organization_results | |
| def validate_file_naming_conventions(self) -> Dict[str, List[str]]: | |
| """Validate consistent file naming throughout project.""" | |
| naming_issues = { | |
| 'non_snake_case_files': [], | |
| 'inconsistent_naming': [], | |
| 'good_examples': [] | |
| } | |
| # Check Python files for snake_case naming | |
| for py_file in self.root_path.rglob('*.py'): | |
| # Skip __init__.py and other special files | |
| if py_file.name.startswith('__') and py_file.name.endswith('__'): | |
| continue | |
| # Check if filename follows snake_case | |
| filename = py_file.stem | |
| if not self._is_snake_case(filename): | |
| relative_path = py_file.relative_to(self.root_path) | |
| naming_issues['non_snake_case_files'].append(str(relative_path)) | |
| else: | |
| relative_path = py_file.relative_to(self.root_path) | |
| naming_issues['good_examples'].append(str(relative_path)) | |
| # Check directory names | |
| for directory in self.root_path.rglob('*'): | |
| if directory.is_dir() and not directory.name.startswith('.'): | |
| if not self._is_snake_case(directory.name) and directory.name not in ['docs', 'tests', 'scripts', 'archive', 'analytics']: | |
| relative_path = directory.relative_to(self.root_path) | |
| naming_issues['inconsistent_naming'].append(f"Directory: {relative_path}") | |
| return naming_issues | |
| def _is_snake_case(self, name: str) -> bool: | |
| """Check if a name follows snake_case convention.""" | |
| # Allow numbers and underscores, but no uppercase or hyphens | |
| return name.islower() and '_' not in name or (name.replace('_', '').replace('-', '').isalnum() and name.islower()) | |
| def analyze_directory_contents(self) -> Dict[str, Any]: | |
| """Analyze contents of key directories.""" | |
| directory_analysis = {} | |
| key_directories = ['tests', 'docs', 'scripts', 'analytics', 'archive'] | |
| for dir_name in key_directories: | |
| dir_path = self.root_path / dir_name | |
| if dir_path.exists(): | |
| directory_analysis[dir_name] = { | |
| 'exists': True, | |
| 'file_count': len(list(dir_path.rglob('*'))), | |
| 'subdirectories': [d.name for d in dir_path.iterdir() if d.is_dir()], | |
| 'python_files': [f.name for f in dir_path.rglob('*.py')], | |
| 'other_files': [f.name for f in dir_path.rglob('*') if f.is_file() and not f.name.endswith('.py')] | |
| } | |
| else: | |
| directory_analysis[dir_name] = {'exists': False} | |
| return directory_analysis | |
| def check_hugging_face_compatibility(self) -> Dict[str, Any]: | |
| """Check compatibility with Hugging Face Spaces requirements.""" | |
| hf_compatibility = { | |
| 'app_py_at_root': (self.root_path / 'app.py').exists(), | |
| 'requirements_txt_at_root': (self.root_path / 'requirements.txt').exists(), | |
| 'readme_present': (self.root_path / 'README.md').exists(), | |
| 'simple_import_structure': True, # Will be validated separately | |
| 'issues': [] | |
| } | |
| # Check for complex nested structures that might cause import issues | |
| deep_nesting = [] | |
| for path in self.root_path.rglob('*.py'): | |
| relative_path = path.relative_to(self.root_path) | |
| if len(relative_path.parts) > 3: # More than 3 levels deep | |
| deep_nesting.append(str(relative_path)) | |
| if deep_nesting: | |
| hf_compatibility['simple_import_structure'] = False | |
| hf_compatibility['issues'].append(f"Deep nesting found: {deep_nesting}") | |
| return hf_compatibility | |
| def generate_structure_summary(self) -> Dict[str, Any]: | |
| """Generate a summary of the current project structure.""" | |
| summary = { | |
| 'timestamp': datetime.now().isoformat(), | |
| 'root_item_count': 0, | |
| 'root_items': [], | |
| 'hidden_items': [], | |
| 'directory_organization': {}, | |
| 'naming_validation': {}, | |
| 'directory_contents': {}, | |
| 'hf_compatibility': {}, | |
| 'overall_score': 0, | |
| 'recommendations': [] | |
| } | |
| # Count root items | |
| count, items, hidden = self.count_root_items() | |
| summary['root_item_count'] = count | |
| summary['root_items'] = sorted(items) | |
| summary['hidden_items'] = sorted(hidden) | |
| # Validate organization | |
| summary['directory_organization'] = self.validate_directory_organization() | |
| # Validate naming | |
| summary['naming_validation'] = self.validate_file_naming_conventions() | |
| # Analyze directory contents | |
| summary['directory_contents'] = self.analyze_directory_contents() | |
| # Check HF compatibility | |
| summary['hf_compatibility'] = self.check_hugging_face_compatibility() | |
| # Calculate overall score and recommendations | |
| summary['overall_score'] = self._calculate_score(summary) | |
| summary['recommendations'] = self._generate_recommendations(summary) | |
| return summary | |
| def _calculate_score(self, summary: Dict[str, Any]) -> int: | |
| """Calculate overall project structure score (0-100).""" | |
| score = 100 | |
| # Deduct points for too many root items | |
| if summary['root_item_count'] > 10: | |
| score -= (summary['root_item_count'] - 10) * 5 | |
| # Deduct points for missing expected items | |
| missing_items = len(summary['directory_organization']['missing_items']) | |
| score -= missing_items * 10 | |
| # Deduct points for naming issues | |
| naming_issues = len(summary['naming_validation']['non_snake_case_files']) | |
| score -= naming_issues * 2 | |
| # Deduct points for HF compatibility issues | |
| if not summary['hf_compatibility']['app_py_at_root']: | |
| score -= 20 | |
| if not summary['hf_compatibility']['requirements_txt_at_root']: | |
| score -= 15 | |
| if not summary['hf_compatibility']['simple_import_structure']: | |
| score -= 10 | |
| return max(0, score) | |
| def _generate_recommendations(self, summary: Dict[str, Any]) -> List[str]: | |
| """Generate recommendations based on validation results.""" | |
| recommendations = [] | |
| if summary['root_item_count'] > 10: | |
| recommendations.append(f"Reduce root directory items from {summary['root_item_count']} to under 10") | |
| if summary['directory_organization']['missing_items']: | |
| recommendations.append(f"Add missing expected items: {', '.join(summary['directory_organization']['missing_items'])}") | |
| if summary['directory_organization']['unexpected_items']: | |
| recommendations.append(f"Consider moving or removing unexpected root items: {', '.join(summary['directory_organization']['unexpected_items'])}") | |
| if summary['naming_validation']['non_snake_case_files']: | |
| recommendations.append("Rename files to follow snake_case convention") | |
| if not summary['hf_compatibility']['app_py_at_root']: | |
| recommendations.append("Ensure app.py is at root level for Hugging Face Spaces compatibility") | |
| return recommendations | |
| def main(): | |
| """Main function to run project structure validation.""" | |
| validator = ProjectStructureValidator() | |
| print("π Validating Project Structure...") | |
| print("=" * 50) | |
| # Generate comprehensive summary | |
| summary = validator.generate_structure_summary() | |
| # Display results | |
| print(f"\nπ VALIDATION RESULTS") | |
| print(f"Overall Score: {summary['overall_score']}/100") | |
| print(f"Root Directory Items: {summary['root_item_count']}/10 (target: <10)") | |
| print(f"\nπ Root Directory Contents:") | |
| for item in summary['root_items']: | |
| print(f" β’ {item}") | |
| if summary['hidden_items']: | |
| print(f"\nπ Hidden Items (not counted):") | |
| for item in summary['hidden_items']: | |
| print(f" β’ {item}") | |
| print(f"\nβ Expected Items Present:") | |
| for item, details in summary['directory_organization']['expected_present'].items(): | |
| status = "β" if details['correct'] else "β οΈ" | |
| print(f" {status} {item} ({details['actual_type']})") | |
| if summary['directory_organization']['missing_items']: | |
| print(f"\nβ Missing Expected Items:") | |
| for item in summary['directory_organization']['missing_items']: | |
| print(f" β’ {item}") | |
| if summary['directory_organization']['unexpected_items']: | |
| print(f"\nβ οΈ Unexpected Root Items:") | |
| for item in summary['directory_organization']['unexpected_items']: | |
| print(f" β’ {item}") | |
| print(f"\nποΈ Directory Structure Analysis:") | |
| for dir_name, details in summary['directory_contents'].items(): | |
| if details['exists']: | |
| print(f" π {dir_name}/") | |
| print(f" Files: {details['file_count']}") | |
| if details['subdirectories']: | |
| print(f" Subdirs: {', '.join(details['subdirectories'])}") | |
| print(f"\nπ Hugging Face Spaces Compatibility:") | |
| hf = summary['hf_compatibility'] | |
| print(f" app.py at root: {'β' if hf['app_py_at_root'] else 'β'}") | |
| print(f" requirements.txt at root: {'β' if hf['requirements_txt_at_root'] else 'β'}") | |
| print(f" README.md present: {'β' if hf['readme_present'] else 'β'}") | |
| print(f" Simple import structure: {'β' if hf['simple_import_structure'] else 'β'}") | |
| if summary['recommendations']: | |
| print(f"\nπ‘ Recommendations:") | |
| for i, rec in enumerate(summary['recommendations'], 1): | |
| print(f" {i}. {rec}") | |
| # Save detailed results to file | |
| output_file = "FINAL_STRUCTURE_VALIDATION.json" | |
| with open(output_file, 'w') as f: | |
| json.dump(summary, f, indent=2) | |
| print(f"\nπ Detailed results saved to: {output_file}") | |
| return summary | |
| if __name__ == "__main__": | |
| main() |