Rewrite CI to lint/typecheck/audit/test only; add HPC deployment path

- 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>
This commit is contained in:
2026-07-22 15:29:55 +02:00
parent 4ba321b327
commit 3b9d1ef1a8
27 changed files with 752 additions and 915 deletions
+38 -37
View File
@@ -6,86 +6,87 @@ This script validates metadata files in the gallery source directories,
checking for proper YAML/JSON syntax and common field validation.
"""
import sys
import json
import yaml
import sys
from pathlib import Path
from typing import Dict, Any, List
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:
with file_path.open("r", encoding="utf-8") as f:
suffix_lower = file_path.suffix.lower()
if suffix_lower in ['.yaml', '.yml']:
if suffix_lower in [".yaml", ".yml"]:
data = yaml.safe_load(f)
elif suffix_lower == '.json':
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):
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):
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):
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 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:
if file_path.name.startswith("meta.") or file_path.stem != file_path.name:
metadata_files.append(file_path)
return metadata_files
@@ -94,32 +95,32 @@ def main():
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
@@ -127,10 +128,10 @@ def main():
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)