style: apply black and isort formatting across entire codebase
- Standardize quote style (single to double quotes) - Reorder and group imports alphabetically - Fix line breaks and indentation for consistency - Apply PEP 8 formatting standards Also updated Makefile to exclude both venv and .venv from code quality checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -22,15 +22,17 @@ import argparse
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Tuple
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class Severity(Enum):
|
||||
"""Validation severity levels"""
|
||||
|
||||
ERROR = "error"
|
||||
WARNING = "warning"
|
||||
INFO = "info"
|
||||
@@ -39,6 +41,7 @@ class Severity(Enum):
|
||||
@dataclass
|
||||
class Violation:
|
||||
"""Represents an architectural rule violation"""
|
||||
|
||||
rule_id: str
|
||||
rule_name: str
|
||||
severity: Severity
|
||||
@@ -52,6 +55,7 @@ class Violation:
|
||||
@dataclass
|
||||
class ValidationResult:
|
||||
"""Results of architecture validation"""
|
||||
|
||||
violations: List[Violation] = field(default_factory=list)
|
||||
files_checked: int = 0
|
||||
rules_applied: int = 0
|
||||
@@ -82,7 +86,7 @@ class ArchitectureValidator:
|
||||
print(f"❌ Configuration file not found: {self.config_path}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(self.config_path, 'r') as f:
|
||||
with open(self.config_path, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
print(f"📋 Loaded architecture rules: {config.get('project', 'unknown')}")
|
||||
@@ -126,7 +130,7 @@ class ArchitectureValidator:
|
||||
continue
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# API-001: Check for Pydantic model usage
|
||||
self._check_pydantic_usage(file_path, content, lines)
|
||||
@@ -147,8 +151,8 @@ class ArchitectureValidator:
|
||||
return
|
||||
|
||||
# Check for response_model in route decorators
|
||||
route_pattern = r'@router\.(get|post|put|delete|patch)'
|
||||
dict_return_pattern = r'return\s+\{.*\}'
|
||||
route_pattern = r"@router\.(get|post|put|delete|patch)"
|
||||
dict_return_pattern = r"return\s+\{.*\}"
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Check for dict returns in endpoints
|
||||
@@ -166,47 +170,51 @@ class ArchitectureValidator:
|
||||
if re.search(dict_return_pattern, func_line):
|
||||
self._add_violation(
|
||||
rule_id="API-001",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=j + 1,
|
||||
message="Endpoint returns raw dict instead of Pydantic model",
|
||||
context=func_line.strip(),
|
||||
suggestion="Define a Pydantic response model and use response_model parameter"
|
||||
suggestion="Define a Pydantic response model and use response_model parameter",
|
||||
)
|
||||
|
||||
def _check_no_business_logic_in_endpoints(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_no_business_logic_in_endpoints(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""API-002: Ensure no business logic in endpoints"""
|
||||
rule = self._get_rule("API-002")
|
||||
if not rule:
|
||||
return
|
||||
|
||||
anti_patterns = [
|
||||
(r'db\.add\(', "Database operations should be in service layer"),
|
||||
(r'db\.commit\(\)', "Database commits should be in service layer"),
|
||||
(r'db\.query\(', "Database queries should be in service layer"),
|
||||
(r'db\.execute\(', "Database operations should be in service layer"),
|
||||
(r"db\.add\(", "Database operations should be in service layer"),
|
||||
(r"db\.commit\(\)", "Database commits should be in service layer"),
|
||||
(r"db\.query\(", "Database queries should be in service layer"),
|
||||
(r"db\.execute\(", "Database operations should be in service layer"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Skip service method calls (allowed)
|
||||
if '_service.' in line or 'service.' in line:
|
||||
if "_service." in line or "service." in line:
|
||||
continue
|
||||
|
||||
for pattern, message in anti_patterns:
|
||||
if re.search(pattern, line):
|
||||
self._add_violation(
|
||||
rule_id="API-002",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message=message,
|
||||
context=line.strip(),
|
||||
suggestion="Move database operations to service layer"
|
||||
suggestion="Move database operations to service layer",
|
||||
)
|
||||
|
||||
def _check_endpoint_exception_handling(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_endpoint_exception_handling(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""API-003: Check proper exception handling in endpoints"""
|
||||
rule = self._get_rule("API-003")
|
||||
if not rule:
|
||||
@@ -222,40 +230,41 @@ class ArchitectureValidator:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
# Check if it's a route handler
|
||||
has_router_decorator = any(
|
||||
isinstance(d, ast.Call) and
|
||||
isinstance(d.func, ast.Attribute) and
|
||||
getattr(d.func.value, 'id', None) == 'router'
|
||||
isinstance(d, ast.Call)
|
||||
and isinstance(d.func, ast.Attribute)
|
||||
and getattr(d.func.value, "id", None) == "router"
|
||||
for d in node.decorator_list
|
||||
)
|
||||
|
||||
if has_router_decorator:
|
||||
# Check if function body has try/except
|
||||
has_try_except = any(
|
||||
isinstance(child, ast.Try)
|
||||
for child in ast.walk(node)
|
||||
isinstance(child, ast.Try) for child in ast.walk(node)
|
||||
)
|
||||
|
||||
# Check if function calls service methods
|
||||
has_service_call = any(
|
||||
isinstance(child, ast.Call) and
|
||||
isinstance(child.func, ast.Attribute) and
|
||||
'service' in getattr(child.func.value, 'id', '').lower()
|
||||
isinstance(child, ast.Call)
|
||||
and isinstance(child.func, ast.Attribute)
|
||||
and "service" in getattr(child.func.value, "id", "").lower()
|
||||
for child in ast.walk(node)
|
||||
)
|
||||
|
||||
if has_service_call and not has_try_except:
|
||||
self._add_violation(
|
||||
rule_id="API-003",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.WARNING,
|
||||
file_path=file_path,
|
||||
line_number=node.lineno,
|
||||
message=f"Endpoint '{node.name}' calls service but lacks exception handling",
|
||||
context=f"def {node.name}(...)",
|
||||
suggestion="Wrap service calls in try/except and convert to HTTPException"
|
||||
suggestion="Wrap service calls in try/except and convert to HTTPException",
|
||||
)
|
||||
|
||||
def _check_endpoint_authentication(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_endpoint_authentication(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""API-004: Check authentication on endpoints"""
|
||||
rule = self._get_rule("API-004")
|
||||
if not rule:
|
||||
@@ -264,24 +273,28 @@ class ArchitectureValidator:
|
||||
# This is a warning-level check
|
||||
# Look for endpoints without Depends(get_current_*)
|
||||
for i, line in enumerate(lines, 1):
|
||||
if '@router.' in line and ('post' in line or 'put' in line or 'delete' in line):
|
||||
if "@router." in line and (
|
||||
"post" in line or "put" in line or "delete" in line
|
||||
):
|
||||
# Check next 5 lines for auth
|
||||
has_auth = False
|
||||
for j in range(i, min(i + 5, len(lines))):
|
||||
if 'Depends(get_current_' in lines[j]:
|
||||
if "Depends(get_current_" in lines[j]:
|
||||
has_auth = True
|
||||
break
|
||||
|
||||
if not has_auth and 'include_in_schema=False' not in ' '.join(lines[i:i+5]):
|
||||
if not has_auth and "include_in_schema=False" not in " ".join(
|
||||
lines[i : i + 5]
|
||||
):
|
||||
self._add_violation(
|
||||
rule_id="API-004",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.WARNING,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message="Endpoint may be missing authentication",
|
||||
context=line.strip(),
|
||||
suggestion="Add Depends(get_current_user) or similar if endpoint should be protected"
|
||||
suggestion="Add Depends(get_current_user) or similar if endpoint should be protected",
|
||||
)
|
||||
|
||||
def _validate_service_layer(self, target_path: Path):
|
||||
@@ -296,7 +309,7 @@ class ArchitectureValidator:
|
||||
continue
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# SVC-001: No HTTPException in services
|
||||
self._check_no_http_exception_in_services(file_path, content, lines)
|
||||
@@ -307,38 +320,45 @@ class ArchitectureValidator:
|
||||
# SVC-003: DB session as parameter
|
||||
self._check_db_session_parameter(file_path, content, lines)
|
||||
|
||||
def _check_no_http_exception_in_services(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_no_http_exception_in_services(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""SVC-001: Services must not raise HTTPException"""
|
||||
rule = self._get_rule("SVC-001")
|
||||
if not rule:
|
||||
return
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
if 'raise HTTPException' in line:
|
||||
if "raise HTTPException" in line:
|
||||
self._add_violation(
|
||||
rule_id="SVC-001",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message="Service raises HTTPException - use domain exceptions instead",
|
||||
context=line.strip(),
|
||||
suggestion="Create custom exception class (e.g., VendorNotFoundError) and raise that"
|
||||
suggestion="Create custom exception class (e.g., VendorNotFoundError) and raise that",
|
||||
)
|
||||
|
||||
if 'from fastapi import HTTPException' in line or 'from fastapi.exceptions import HTTPException' in line:
|
||||
if (
|
||||
"from fastapi import HTTPException" in line
|
||||
or "from fastapi.exceptions import HTTPException" in line
|
||||
):
|
||||
self._add_violation(
|
||||
rule_id="SVC-001",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message="Service imports HTTPException - services should not know about HTTP",
|
||||
context=line.strip(),
|
||||
suggestion="Remove HTTPException import and use domain exceptions"
|
||||
suggestion="Remove HTTPException import and use domain exceptions",
|
||||
)
|
||||
|
||||
def _check_service_exceptions(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_service_exceptions(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""SVC-002: Check for proper exception handling"""
|
||||
rule = self._get_rule("SVC-002")
|
||||
if not rule:
|
||||
@@ -346,19 +366,21 @@ class ArchitectureValidator:
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Check for generic Exception raises
|
||||
if re.match(r'\s*raise Exception\(', line):
|
||||
if re.match(r"\s*raise Exception\(", line):
|
||||
self._add_violation(
|
||||
rule_id="SVC-002",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.WARNING,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message="Service raises generic Exception - use specific domain exception",
|
||||
context=line.strip(),
|
||||
suggestion="Create custom exception class for this error case"
|
||||
suggestion="Create custom exception class for this error case",
|
||||
)
|
||||
|
||||
def _check_db_session_parameter(self, file_path: Path, content: str, lines: List[str]):
|
||||
def _check_db_session_parameter(
|
||||
self, file_path: Path, content: str, lines: List[str]
|
||||
):
|
||||
"""SVC-003: Service methods should accept db session as parameter"""
|
||||
rule = self._get_rule("SVC-003")
|
||||
if not rule:
|
||||
@@ -366,16 +388,16 @@ class ArchitectureValidator:
|
||||
|
||||
# Check for SessionLocal() creation in service files
|
||||
for i, line in enumerate(lines, 1):
|
||||
if 'SessionLocal()' in line and 'class' not in line:
|
||||
if "SessionLocal()" in line and "class" not in line:
|
||||
self._add_violation(
|
||||
rule_id="SVC-003",
|
||||
rule_name=rule['name'],
|
||||
rule_name=rule["name"],
|
||||
severity=Severity.ERROR,
|
||||
file_path=file_path,
|
||||
line_number=i,
|
||||
message="Service creates database session internally",
|
||||
context=line.strip(),
|
||||
suggestion="Accept db: Session as method parameter instead"
|
||||
suggestion="Accept db: Session as method parameter instead",
|
||||
)
|
||||
|
||||
def _validate_models(self, target_path: Path):
|
||||
@@ -391,11 +413,11 @@ class ArchitectureValidator:
|
||||
continue
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# Check for mixing SQLAlchemy and Pydantic
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'class.*\(Base.*,.*BaseModel.*\)', line):
|
||||
if re.search(r"class.*\(Base.*,.*BaseModel.*\)", line):
|
||||
self._add_violation(
|
||||
rule_id="MDL-002",
|
||||
rule_name="Separate SQLAlchemy and Pydantic models",
|
||||
@@ -404,7 +426,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Model mixes SQLAlchemy Base and Pydantic BaseModel",
|
||||
context=line.strip(),
|
||||
suggestion="Keep SQLAlchemy models and Pydantic models separate"
|
||||
suggestion="Keep SQLAlchemy models and Pydantic models separate",
|
||||
)
|
||||
|
||||
def _validate_exceptions(self, target_path: Path):
|
||||
@@ -418,11 +440,11 @@ class ArchitectureValidator:
|
||||
continue
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# EXC-002: Check for bare except
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.match(r'\s*except\s*:', line):
|
||||
if re.match(r"\s*except\s*:", line):
|
||||
self._add_violation(
|
||||
rule_id="EXC-002",
|
||||
rule_name="No bare except clauses",
|
||||
@@ -431,7 +453,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Bare except clause catches all exceptions including system exits",
|
||||
context=line.strip(),
|
||||
suggestion="Specify exception type: except ValueError: or except Exception:"
|
||||
suggestion="Specify exception type: except ValueError: or except Exception:",
|
||||
)
|
||||
|
||||
def _validate_javascript(self, target_path: Path):
|
||||
@@ -443,11 +465,16 @@ class ArchitectureValidator:
|
||||
|
||||
for file_path in js_files:
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# JS-001: Check for window.apiClient
|
||||
for i, line in enumerate(lines, 1):
|
||||
if 'window.apiClient' in line and '//' not in line[:line.find('window.apiClient')] if 'window.apiClient' in line else True:
|
||||
if (
|
||||
"window.apiClient" in line
|
||||
and "//" not in line[: line.find("window.apiClient")]
|
||||
if "window.apiClient" in line
|
||||
else True
|
||||
):
|
||||
self._add_violation(
|
||||
rule_id="JS-001",
|
||||
rule_name="Use apiClient directly",
|
||||
@@ -456,14 +483,14 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Use apiClient directly instead of window.apiClient",
|
||||
context=line.strip(),
|
||||
suggestion="Replace window.apiClient with apiClient"
|
||||
suggestion="Replace window.apiClient with apiClient",
|
||||
)
|
||||
|
||||
# JS-002: Check for console usage
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'console\.(log|warn|error)', line):
|
||||
if re.search(r"console\.(log|warn|error)", line):
|
||||
# Skip if it's a comment or bootstrap message
|
||||
if '//' in line or '✅' in line or 'eslint-disable' in line:
|
||||
if "//" in line or "✅" in line or "eslint-disable" in line:
|
||||
continue
|
||||
|
||||
self._add_violation(
|
||||
@@ -474,7 +501,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message="Use centralized logger instead of console",
|
||||
context=line.strip()[:80],
|
||||
suggestion="Use window.LogConfig.createLogger('moduleName')"
|
||||
suggestion="Use window.LogConfig.createLogger('moduleName')",
|
||||
)
|
||||
|
||||
def _validate_templates(self, target_path: Path):
|
||||
@@ -486,14 +513,16 @@ class ArchitectureValidator:
|
||||
|
||||
for file_path in template_files:
|
||||
# Skip base template and partials
|
||||
if 'base.html' in file_path.name or 'partials' in str(file_path):
|
||||
if "base.html" in file_path.name or "partials" in str(file_path):
|
||||
continue
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
|
||||
# TPL-001: Check for extends
|
||||
has_extends = any('{% extends' in line and 'admin/base.html' in line for line in lines)
|
||||
has_extends = any(
|
||||
"{% extends" in line and "admin/base.html" in line for line in lines
|
||||
)
|
||||
|
||||
if not has_extends:
|
||||
self._add_violation(
|
||||
@@ -504,23 +533,29 @@ class ArchitectureValidator:
|
||||
line_number=1,
|
||||
message="Admin template does not extend admin/base.html",
|
||||
context=file_path.name,
|
||||
suggestion="Add {% extends 'admin/base.html' %} at the top"
|
||||
suggestion="Add {% extends 'admin/base.html' %} at the top",
|
||||
)
|
||||
|
||||
def _get_rule(self, rule_id: str) -> Dict[str, Any]:
|
||||
"""Get rule configuration by ID"""
|
||||
# Look in different rule categories
|
||||
for category in ['api_endpoint_rules', 'service_layer_rules', 'model_rules',
|
||||
'exception_rules', 'javascript_rules', 'template_rules']:
|
||||
for category in [
|
||||
"api_endpoint_rules",
|
||||
"service_layer_rules",
|
||||
"model_rules",
|
||||
"exception_rules",
|
||||
"javascript_rules",
|
||||
"template_rules",
|
||||
]:
|
||||
rules = self.config.get(category, [])
|
||||
for rule in rules:
|
||||
if rule.get('id') == rule_id:
|
||||
if rule.get("id") == rule_id:
|
||||
return rule
|
||||
return None
|
||||
|
||||
def _should_ignore_file(self, file_path: Path) -> bool:
|
||||
"""Check if file should be ignored"""
|
||||
ignore_patterns = self.config.get('ignore', {}).get('files', [])
|
||||
ignore_patterns = self.config.get("ignore", {}).get("files", [])
|
||||
|
||||
for pattern in ignore_patterns:
|
||||
if file_path.match(pattern):
|
||||
@@ -528,9 +563,17 @@ class ArchitectureValidator:
|
||||
|
||||
return False
|
||||
|
||||
def _add_violation(self, rule_id: str, rule_name: str, severity: Severity,
|
||||
file_path: Path, line_number: int, message: str,
|
||||
context: str = "", suggestion: str = ""):
|
||||
def _add_violation(
|
||||
self,
|
||||
rule_id: str,
|
||||
rule_name: str,
|
||||
severity: Severity,
|
||||
file_path: Path,
|
||||
line_number: int,
|
||||
message: str,
|
||||
context: str = "",
|
||||
suggestion: str = "",
|
||||
):
|
||||
"""Add a violation to results"""
|
||||
violation = Violation(
|
||||
rule_id=rule_id,
|
||||
@@ -540,7 +583,7 @@ class ArchitectureValidator:
|
||||
line_number=line_number,
|
||||
message=message,
|
||||
context=context,
|
||||
suggestion=suggestion
|
||||
suggestion=suggestion,
|
||||
)
|
||||
self.result.violations.append(violation)
|
||||
|
||||
@@ -590,24 +633,34 @@ class ArchitectureValidator:
|
||||
|
||||
violations_json = []
|
||||
for v in self.result.violations:
|
||||
rel_path = str(v.file_path.relative_to(self.project_root)) if self.project_root in v.file_path.parents else str(v.file_path)
|
||||
violations_json.append({
|
||||
'rule_id': v.rule_id,
|
||||
'rule_name': v.rule_name,
|
||||
'severity': v.severity.value,
|
||||
'file_path': rel_path,
|
||||
'line_number': v.line_number,
|
||||
'message': v.message,
|
||||
'context': v.context or '',
|
||||
'suggestion': v.suggestion or ''
|
||||
})
|
||||
rel_path = (
|
||||
str(v.file_path.relative_to(self.project_root))
|
||||
if self.project_root in v.file_path.parents
|
||||
else str(v.file_path)
|
||||
)
|
||||
violations_json.append(
|
||||
{
|
||||
"rule_id": v.rule_id,
|
||||
"rule_name": v.rule_name,
|
||||
"severity": v.severity.value,
|
||||
"file_path": rel_path,
|
||||
"line_number": v.line_number,
|
||||
"message": v.message,
|
||||
"context": v.context or "",
|
||||
"suggestion": v.suggestion or "",
|
||||
}
|
||||
)
|
||||
|
||||
output = {
|
||||
'files_checked': self.result.files_checked,
|
||||
'total_violations': len(self.result.violations),
|
||||
'errors': len([v for v in self.result.violations if v.severity == Severity.ERROR]),
|
||||
'warnings': len([v for v in self.result.violations if v.severity == Severity.WARNING]),
|
||||
'violations': violations_json
|
||||
"files_checked": self.result.files_checked,
|
||||
"total_violations": len(self.result.violations),
|
||||
"errors": len(
|
||||
[v for v in self.result.violations if v.severity == Severity.ERROR]
|
||||
),
|
||||
"warnings": len(
|
||||
[v for v in self.result.violations if v.severity == Severity.WARNING]
|
||||
),
|
||||
"violations": violations_json,
|
||||
}
|
||||
|
||||
print(json.dumps(output, indent=2))
|
||||
@@ -616,7 +669,11 @@ class ArchitectureValidator:
|
||||
|
||||
def _print_violation(self, v: Violation):
|
||||
"""Print a single violation"""
|
||||
rel_path = v.file_path.relative_to(self.project_root) if self.project_root in v.file_path.parents else v.file_path
|
||||
rel_path = (
|
||||
v.file_path.relative_to(self.project_root)
|
||||
if self.project_root in v.file_path.parents
|
||||
else v.file_path
|
||||
)
|
||||
|
||||
print(f"\n [{v.rule_id}] {v.rule_name}")
|
||||
print(f" File: {rel_path}:{v.line_number}")
|
||||
@@ -634,40 +691,40 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate architecture patterns in codebase",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__
|
||||
epilog=__doc__,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'path',
|
||||
nargs='?',
|
||||
"path",
|
||||
nargs="?",
|
||||
type=Path,
|
||||
default=Path.cwd(),
|
||||
help="Path to validate (default: current directory)"
|
||||
help="Path to validate (default: current directory)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-c', '--config',
|
||||
"-c",
|
||||
"--config",
|
||||
type=Path,
|
||||
default=Path.cwd() / '.architecture-rules.yaml',
|
||||
help="Path to architecture rules config (default: .architecture-rules.yaml)"
|
||||
default=Path.cwd() / ".architecture-rules.yaml",
|
||||
help="Path to architecture rules config (default: .architecture-rules.yaml)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help="Show detailed output including context"
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Show detailed output including context",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--errors-only',
|
||||
action='store_true',
|
||||
help="Only show errors, suppress warnings"
|
||||
"--errors-only", action="store_true", help="Only show errors, suppress warnings"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--json',
|
||||
action='store_true',
|
||||
help="Output results as JSON (for programmatic use)"
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Output results as JSON (for programmatic use)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -687,5 +744,5 @@ def main():
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user