3b9d1ef1a8
- Replace the Docker build/publish CI stages with ruff (lint + format check), ty (type check), pip-audit, and pytest run directly against python:3.11-slim; Docker remains for manual/server deployment only. - Swap black/pylint/mypy for ruff/ty across pyproject.toml, and fix every resulting lint, format, and type diagnostic in gallery/ and plotstyle/. - Fix tests broken/stale from before the package restructuring: wrong `utils.*` import paths, mock patch targets pointed at the wrong module, and PDF-conversion tests still assuming ImageMagick instead of the current PyMuPDF-first path. Drop test_container.py (obsolete Docker-container smoke tests, fully superseded elsewhere). - Add a plain-venv + systemd --user timer deployment path (deploy/systemd/) for HPC login nodes without a Docker daemon, where public_html is already served by existing infrastructure. - Document both in CLAUDE.md, including running CI's checks locally before committing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
141 lines
3.8 KiB
Python
141 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Metadata Validation Utility
|
|
|
|
This script validates metadata files in the gallery source directories,
|
|
checking for proper YAML/JSON syntax and common field validation.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
import yaml
|
|
|
|
|
|
def validate_metadata_file(file_path: Path) -> tuple[bool, List[str]]:
|
|
"""
|
|
Validate a single metadata file.
|
|
|
|
Args:
|
|
file_path: Path to the metadata file
|
|
|
|
Returns:
|
|
Tuple of (is_valid, error_messages)
|
|
"""
|
|
errors = []
|
|
|
|
if not file_path.exists():
|
|
errors.append(f"File does not exist: {file_path}")
|
|
return False, errors
|
|
|
|
try:
|
|
with file_path.open("r", encoding="utf-8") as f:
|
|
suffix_lower = file_path.suffix.lower()
|
|
if suffix_lower in [".yaml", ".yml"]:
|
|
data = yaml.safe_load(f)
|
|
elif suffix_lower == ".json":
|
|
data = json.load(f)
|
|
else:
|
|
errors.append(f"Unsupported file format: {file_path}")
|
|
return False, errors
|
|
|
|
if data is None:
|
|
errors.append(f"Empty metadata file: {file_path}")
|
|
return False, errors
|
|
|
|
# Basic validation
|
|
if not isinstance(data, dict):
|
|
errors.append(f"Metadata must be a dictionary: {file_path}")
|
|
return False, errors
|
|
|
|
# Check for common issues
|
|
if "title" in data and not isinstance(data["title"], str):
|
|
errors.append(f"Title must be a string: {file_path}")
|
|
|
|
if "tags" in data and not isinstance(data["tags"], list):
|
|
errors.append(f"Tags must be a list: {file_path}")
|
|
|
|
if "author" in data and not isinstance(data["author"], dict):
|
|
errors.append(f"Author must be a dictionary: {file_path}")
|
|
|
|
except (yaml.YAMLError, json.JSONDecodeError) as e:
|
|
errors.append(f"Parse error in {file_path}: {e}")
|
|
return False, errors
|
|
except Exception as e:
|
|
errors.append(f"Unexpected error reading {file_path}: {e}")
|
|
return False, errors
|
|
|
|
return len(errors) == 0, errors
|
|
|
|
|
|
def find_metadata_files(root_dir: Path) -> List[Path]:
|
|
"""
|
|
Find all metadata files in a directory tree.
|
|
|
|
Args:
|
|
root_dir: Root directory to search
|
|
|
|
Returns:
|
|
List of metadata file paths
|
|
"""
|
|
metadata_files = []
|
|
|
|
for pattern in ["**/*.yaml", "**/*.yml", "**/*.json"]:
|
|
for file_path in root_dir.glob(pattern):
|
|
if file_path.name.startswith("meta.") or file_path.stem != file_path.name:
|
|
metadata_files.append(file_path)
|
|
|
|
return metadata_files
|
|
|
|
|
|
def main():
|
|
"""Main validation function."""
|
|
if len(sys.argv) != 2:
|
|
print("Usage: python validate_metadata.py <directory>")
|
|
sys.exit(1)
|
|
|
|
root_dir = Path(sys.argv[1])
|
|
|
|
if not root_dir.exists():
|
|
print(f"Error: Directory does not exist: {root_dir}")
|
|
sys.exit(1)
|
|
|
|
if not root_dir.is_dir():
|
|
print(f"Error: Not a directory: {root_dir}")
|
|
sys.exit(1)
|
|
|
|
print(f"Validating metadata files in: {root_dir}")
|
|
print("-" * 50)
|
|
|
|
metadata_files = find_metadata_files(root_dir)
|
|
|
|
if not metadata_files:
|
|
print("No metadata files found.")
|
|
return
|
|
|
|
total_files = len(metadata_files)
|
|
valid_files = 0
|
|
|
|
for file_path in metadata_files:
|
|
is_valid, errors = validate_metadata_file(file_path)
|
|
|
|
if is_valid:
|
|
print(f"✓ {file_path.relative_to(root_dir)}")
|
|
valid_files += 1
|
|
else:
|
|
print(f"✗ {file_path.relative_to(root_dir)}")
|
|
for error in errors:
|
|
print(f" - {error}")
|
|
|
|
print("-" * 50)
|
|
print(f"Summary: {valid_files}/{total_files} files valid")
|
|
|
|
if valid_files != total_files:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|