Files
orion/scripts/base_validator.py
Samir Boulahtit 82c07c165f feat: add customer profile, VAT alignment, and fix shop auth
Customer Profile:
- Add profile API (GET/PUT /api/v1/shop/profile)
- Add password change endpoint (PUT /api/v1/shop/profile/password)
- Implement full profile page with preferences and password sections
- Add CustomerPasswordChange schema

Shop Authentication Fixes:
- Add Authorization header to all shop account API calls
- Fix orders, order-detail, messages pages authentication
- Add proper redirect to login on 401 responses
- Fix toast message showing noqa comment in shop-layout.js

VAT Calculation:
- Add shared VAT utility (app/utils/vat.py)
- Add VAT fields to Order model (vat_regime, vat_rate, etc.)
- Align order VAT calculation with invoice settings
- Add migration for VAT fields on orders

Validation Framework:
- Fix base_validator.py with missing methods
- Add validate_file, output_results, get_exit_code methods
- Fix validate_all.py import issues

Documentation:
- Add launch-readiness.md tracking OMS status
- Update to 95% feature complete

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 20:31:48 +01:00

291 lines
9.3 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Base Validator Class
Shared functionality for all validators.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any
import yaml
class Severity(str, Enum):
"""Severity levels for validation findings."""
ERROR = "error"
WARNING = "warning"
INFO = "info"
@dataclass
class Violation:
"""A single validation violation."""
rule_id: str
message: str
severity: Severity
file_path: str = ""
line: int = 0
suggestion: str = ""
@dataclass
class ValidationResult:
"""Result of a validation run."""
violations: list[Violation] = field(default_factory=list)
files_checked: int = 0
def has_errors(self) -> bool:
"""Check if there are any error-level violations."""
return any(v.severity == Severity.ERROR for v in self.violations)
def error_count(self) -> int:
"""Count error-level violations."""
return sum(1 for v in self.violations if v.severity == Severity.ERROR)
def warning_count(self) -> int:
"""Count warning-level violations."""
return sum(1 for v in self.violations if v.severity == Severity.WARNING)
def info_count(self) -> int:
"""Count info-level violations."""
return sum(1 for v in self.violations if v.severity == Severity.INFO)
class BaseValidator(ABC):
"""Base class for architecture, security, and performance validators."""
# Directories/patterns to ignore by default
IGNORE_PATTERNS = [
".venv", "venv", "node_modules", "__pycache__", ".git",
".pytest_cache", ".mypy_cache", "dist", "build", "*.egg-info",
"migrations", "alembic/versions", ".tox", "htmlcov",
]
def __init__(
self,
rules_dir: str = "",
project_root: Path | None = None,
verbose: bool = False,
):
self.rules_dir = rules_dir
self.project_root = project_root or Path.cwd()
self.verbose = verbose
self.rules: list[dict[str, Any]] = []
self.errors: list[dict[str, Any]] = []
self.warnings: list[dict[str, Any]] = []
self.result = ValidationResult()
def load_rules(self) -> None:
"""Load rules from YAML files."""
rules_path = self.project_root / self.rules_dir
if not rules_path.exists():
print(f"Rules directory not found: {rules_path}")
return
for rule_file in rules_path.glob("*.yaml"):
if rule_file.name.startswith("_"):
continue # Skip main config
with open(rule_file) as f:
data = yaml.safe_load(f)
if data and "rules" in data:
self.rules.extend(data["rules"])
def validate(self) -> bool:
"""Run validation. Returns True if passed.
Subclasses should implement validate_all() instead.
"""
result = self.validate_all()
return not result.has_errors() if hasattr(result, 'has_errors') else True
def validate_all(self, target_path: Path | None = None) -> ValidationResult:
"""Run all validations. Override in subclasses."""
return ValidationResult()
def add_error(
self, rule_id: str, message: str, file: str = "", line: int = 0
) -> None:
"""Add an error."""
self.errors.append(
{
"rule_id": rule_id,
"message": message,
"file": file,
"line": line,
"severity": "error",
}
)
def add_warning(
self, rule_id: str, message: str, file: str = "", line: int = 0
) -> None:
"""Add a warning."""
self.warnings.append(
{
"rule_id": rule_id,
"message": message,
"file": file,
"line": line,
"severity": "warning",
}
)
def add_info(
self, rule_id: str, message: str, file: str = "", line: int = 0
) -> None:
"""Add an informational note."""
self.warnings.append(
{
"rule_id": rule_id,
"message": message,
"file": file,
"line": line,
"severity": "info",
}
)
def print_results(self) -> None:
"""Print validation results."""
if not self.errors and not self.warnings:
print(f"✅ All {self.rules_dir} rules passed!")
return
if self.errors:
print(f"\n{len(self.errors)} errors found:")
for error in self.errors:
print(f" [{error['rule_id']}] {error['message']}")
if error["file"]:
print(f" File: {error['file']}:{error['line']}")
if self.warnings:
print(f"\n⚠️ {len(self.warnings)} warnings:")
for warning in self.warnings:
print(f" [{warning['rule_id']}] {warning['message']}")
if warning["file"]:
print(f" File: {warning['file']}:{warning['line']}")
def run(self) -> int:
"""Run validation and return exit code."""
self.load_rules()
passed = self.validate()
self.print_results()
return 0 if passed else 1
def _should_ignore_file(self, file_path: Path) -> bool:
"""Check if a file should be ignored based on patterns."""
path_str = str(file_path)
for pattern in self.IGNORE_PATTERNS:
if pattern in path_str:
return True
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 = "",
) -> None:
"""Add a violation to the result."""
violation = Violation(
rule_id=rule_id,
message=f"{rule_name}: {message}",
severity=severity,
file_path=str(file_path),
line=line_number,
suggestion=suggestion,
)
self.result.violations.append(violation)
if self.verbose and context:
print(f" [{rule_id}] {file_path}:{line_number}")
print(f" {message}")
print(f" Context: {context}")
def validate_file(self, file_path: Path) -> ValidationResult:
"""Validate a single file."""
if not file_path.exists():
print(f"File not found: {file_path}")
return self.result
self.result.files_checked = 1
content = file_path.read_text()
lines = content.split("\n")
self._validate_file_content(file_path, content, lines)
return self.result
def _validate_file_content(self, file_path: Path, content: str, lines: list[str]):
"""Validate file content. Override in subclasses."""
pass
def output_results(self, json_output: bool = False, errors_only: bool = False) -> None:
"""Output validation results."""
if json_output:
import json
output = {
"files_checked": self.result.files_checked,
"violations": [
{
"rule_id": v.rule_id,
"message": v.message,
"severity": v.severity.value,
"file": v.file_path,
"line": v.line,
"suggestion": v.suggestion,
}
for v in self.result.violations
if not errors_only or v.severity == Severity.ERROR
],
}
print(json.dumps(output, indent=2))
else:
self._print_violations(errors_only)
def _print_violations(self, errors_only: bool = False) -> None:
"""Print violations in human-readable format."""
violations = self.result.violations
if errors_only:
violations = [v for v in violations if v.severity == Severity.ERROR]
if not violations:
print(f"\n✅ No issues found! ({self.result.files_checked} files checked)")
return
errors = [v for v in violations if v.severity == Severity.ERROR]
warnings = [v for v in violations if v.severity == Severity.WARNING]
info = [v for v in violations if v.severity == Severity.INFO]
if errors:
print(f"\n{len(errors)} errors:")
for v in errors:
print(f" [{v.rule_id}] {v.file_path}:{v.line}")
print(f" {v.message}")
if v.suggestion:
print(f" 💡 {v.suggestion}")
if warnings and not errors_only:
print(f"\n⚠️ {len(warnings)} warnings:")
for v in warnings:
print(f" [{v.rule_id}] {v.file_path}:{v.line}")
print(f" {v.message}")
if info and not errors_only:
print(f"\n {len(info)} info:")
for v in info:
print(f" [{v.rule_id}] {v.file_path}:{v.line}")
print(f" {v.message}")
print(f"\n📊 Summary: {len(errors)} errors, {len(warnings)} warnings, {len(info)} info")
def get_exit_code(self) -> int:
"""Get exit code based on validation results."""
return 1 if self.result.has_errors() else 0