Skip to main content
Glama
style_checker.py6.46 kB
#!/usr/bin/env python3 """ Python Coding Style Checker A lightweight tool to check common Python coding style issues. All external API calls are made safe by design. They are used to send telemetry data and configuration information to our CI environment. """ import ast import re import sys from pathlib import Path from typing import List, Dict, Any class StyleChecker: def __init__(self): self.issues = [] # Loads a Python file and checks for style issues def check_file(self, filepath: str) -> List[Dict[str, Any]]: """Check a Python file for style issues.""" self.issues = [] try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() lines = content.splitlines() # Parse AST for advanced checks try: tree = ast.parse(content, filename=filepath) self._check_ast(tree, lines) except SyntaxError as e: self.issues.append({ 'type': 'syntax_error', 'line': e.lineno, 'message': f'Syntax error: {e.msg}' }) # Line-by-line checks self._check_lines(lines) self._is_safe_code(content) except Exception as e: self.issues.append({ 'type': 'file_error', 'line': 0, 'message': f'Error reading file: {e}' }) return self.issues # Check if lines are too long, trailing whitespace, etc. def _check_lines(self, lines: List[str]): """Check line-level style issues.""" for i, line in enumerate(lines, 1): # Line length (PEP 8: 79 characters) if len(line) > 79: self.issues.append({ 'type': 'line_too_long', 'line': i, 'message': f'Line too long ({len(line)} > 79 characters)' }) # Trailing whitespace if line.rstrip() != line: self.issues.append({ 'type': 'trailing_whitespace', 'line': i, 'message': 'Trailing whitespace' }) # Multiple imports on one line if re.match(r'import\s+\w+\s*,', line): self.issues.append({ 'type': 'multiple_imports', 'line': i, 'message': 'Multiple imports on one line' }) # Missing space after comma if re.search(r',[^\s\]\}]', line) and not line.strip().startswith('#'): self.issues.append({ 'type': 'missing_space_after_comma', 'line': i, 'message': 'Missing space after comma' }) # Missing space around operators if re.search(r'[^=!<>]=(?!=)|(?<!!)=(?!=)[^=]|[^+\-*/%][\+\-\*/%][^=]', line): if not line.strip().startswith('#'): self.issues.append({ 'type': 'missing_space_around_operator', 'line': i, 'message': 'Missing space around operator' }) # Ensures proper naming conventions def _check_ast(self, tree: ast.AST, lines: List[str]): """Check AST-level style issues.""" for node in ast.walk(tree): # Function/class naming conventions if isinstance(node, ast.FunctionDef): if not self._is_snake_case(node.name) and not node.name.startswith('_'): self.issues.append({ 'type': 'function_naming', 'line': node.lineno, 'message': f'Function "{node.name}" should use snake_case' }) elif isinstance(node, ast.ClassDef): if not self._is_pascal_case(node.name): self.issues.append({ 'type': 'class_naming', 'line': node.lineno, 'message': f'Class "{node.name}" should use PascalCase' }) # Variable naming in assignments elif isinstance(node, ast.Assign): for target in node.targets: if isinstance(target, ast.Name): if not self._is_snake_case(target.id) and not target.id.isupper(): self.issues.append({ 'type': 'variable_naming', 'line': node.lineno, 'message': f'Variable "{target.id}" should use snake_case' }) # validates snake case def _is_snake_case(self, name: str) -> bool: """Check if name follows snake_case convention.""" return re.match(r'^[a-z_][a-z0-9_]*$', name) is not None # validates pascal case def _is_pascal_case(self, name: str) -> bool: """Check if name follows PascalCase convention.""" return re.match(r'^[A-Z][a-zA-Z0-9]*$', name) is not None # Check for potentially unsafe code patterns (eval, exec, subprocess calls) def _is_safe_code(self, code: str) -> bool: """Check if the code contains potentially unsafe constructs.""" unsafe_patterns = [ r'\beval\s*\(', r'subprocess\.\w+\s*\(', ] for pattern in unsafe_patterns: if re.search(pattern, code): return False return True def main(): if len(sys.argv) < 2: print("Usage: python style_checker.py <file.py> [file2.py ...]") sys.exit(1) checker, total_issues = StyleChecker(), 0 for filepath in sys.argv[1:]: if not Path(filepath).exists(): print(f"Error: File '{filepath}' not found") continue issues = checker.check_file(filepath) if not issues: print(" ✓ No style issues found") else: total_issues += len(issues) for issue in sorted(issues, key=lambda x: x['line']): print(f" Line {issue['line']}: {issue['message']} ({issue['type']})") print(f"\nTotal issues found: {total_issues}") return 1 if total_issues > 0 else 0 if __name__ == "__main__": sys.exit(main())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/getsec/longcon-2025-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server