mirror of
https://github.com/PlaneQuery/OpenAirframes.git
synced 2026-04-27 13:37:10 +02:00
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Validate a community submission from a GitHub issue.
|
|
|
|
This script is called by the GitHub Actions workflow to validate
|
|
submissions when issues are opened or edited.
|
|
|
|
Usage:
|
|
python -m src.contributions.validate_submission --issue-body "..."
|
|
python -m src.contributions.validate_submission --file submission.json
|
|
echo '{"registration_number": "N12345"}' | python -m src.contributions.validate_submission --stdin
|
|
|
|
Environment variables (for GitHub Actions):
|
|
GITHUB_TOKEN: GitHub API token
|
|
GITHUB_REPOSITORY: owner/repo
|
|
ISSUE_NUMBER: Issue number to comment on
|
|
"""
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import urllib.error
|
|
|
|
from .schema import extract_json_from_issue_body, parse_and_validate, load_schema
|
|
|
|
|
|
def github_api_request(method: str, endpoint: str, data: dict | None = None) -> dict:
|
|
"""Make a GitHub API request."""
|
|
token = os.environ.get("GITHUB_TOKEN")
|
|
repo = os.environ.get("GITHUB_REPOSITORY")
|
|
|
|
if not token or not repo:
|
|
raise EnvironmentError("GITHUB_TOKEN and GITHUB_REPOSITORY must be set")
|
|
|
|
url = f"https://api.github.com/repos/{repo}{endpoint}"
|
|
headers = {
|
|
"Authorization": f"token {token}",
|
|
"Accept": "application/vnd.github.v3+json",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
body = json.dumps(data).encode() if data else None
|
|
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
|
|
with urllib.request.urlopen(req) as response:
|
|
return json.loads(response.read())
|
|
|
|
|
|
def add_issue_comment(issue_number: int, body: str) -> None:
|
|
"""Add a comment to a GitHub issue."""
|
|
github_api_request("POST", f"/issues/{issue_number}/comments", {"body": body})
|
|
|
|
|
|
def add_issue_label(issue_number: int, label: str) -> None:
|
|
"""Add a label to a GitHub issue."""
|
|
github_api_request("POST", f"/issues/{issue_number}/labels", {"labels": [label]})
|
|
|
|
|
|
def remove_issue_label(issue_number: int, label: str) -> None:
|
|
"""Remove a label from a GitHub issue."""
|
|
try:
|
|
github_api_request("DELETE", f"/issues/{issue_number}/labels/{label}")
|
|
except urllib.error.HTTPError:
|
|
pass # Label might not exist
|
|
|
|
|
|
def validate_and_report(json_str: str, issue_number: int | None = None) -> bool:
|
|
"""
|
|
Validate JSON and optionally report to GitHub issue.
|
|
|
|
Args:
|
|
json_str: JSON string to validate
|
|
issue_number: Optional issue number to comment on
|
|
|
|
Returns:
|
|
True if validation passed, False otherwise
|
|
"""
|
|
data, errors = parse_and_validate(json_str)
|
|
|
|
if errors:
|
|
error_list = "\n".join(f"- {e}" for e in errors)
|
|
message = f"❌ **Validation Failed**\n\n{error_list}\n\nPlease fix the errors and edit your submission."
|
|
|
|
print(message, file=sys.stderr)
|
|
|
|
if issue_number:
|
|
add_issue_comment(issue_number, message)
|
|
remove_issue_label(issue_number, "validated")
|
|
|
|
return False
|
|
|
|
count = len(data) if isinstance(data, list) else 1
|
|
message = f"✅ **Validation Passed**\n\n{count} submission(s) validated successfully against the schema.\n\nA maintainer can approve this submission by adding the `approved` label."
|
|
|
|
print(message)
|
|
|
|
if issue_number:
|
|
add_issue_comment(issue_number, message)
|
|
add_issue_label(issue_number, "validated")
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Validate community submission JSON")
|
|
source_group = parser.add_mutually_exclusive_group(required=True)
|
|
source_group.add_argument("--issue-body", help="Issue body text containing JSON")
|
|
source_group.add_argument("--file", help="JSON file to validate")
|
|
source_group.add_argument("--stdin", action="store_true", help="Read JSON from stdin")
|
|
|
|
parser.add_argument("--issue-number", type=int, help="GitHub issue number to comment on")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Get JSON string
|
|
if args.issue_body:
|
|
json_str = extract_json_from_issue_body(args.issue_body)
|
|
if not json_str:
|
|
print("❌ Could not extract JSON from issue body", file=sys.stderr)
|
|
if args.issue_number:
|
|
add_issue_comment(
|
|
args.issue_number,
|
|
"❌ **Validation Failed**\n\nCould not extract JSON from submission. "
|
|
"Please ensure your JSON is in the 'Submission JSON' field wrapped in code blocks."
|
|
)
|
|
sys.exit(1)
|
|
elif args.file:
|
|
with open(args.file) as f:
|
|
json_str = f.read()
|
|
else: # stdin
|
|
json_str = sys.stdin.read()
|
|
|
|
# Validate
|
|
success = validate_and_report(json_str, args.issue_number)
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|