Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Patch and validate v0 dataset JSON files for wonders_v2 compatibility.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from collections import Counter | |
| from pathlib import Path | |
| from typing import Any | |
| REQUIRED_FIELDS = [ | |
| "image", | |
| "image_file", | |
| "image_url", | |
| "caption", | |
| "country", | |
| "language", | |
| "category", | |
| "concept", | |
| "timestamp", | |
| "username", | |
| "id", | |
| "excluded", | |
| ] | |
| def _iter_json_files(root: Path) -> list[Path]: | |
| files: list[Path] = [] | |
| for path in root.rglob("*.json"): | |
| if ".cache" in path.parts: | |
| continue | |
| files.append(path) | |
| return sorted(files) | |
| def _validate_record(data: dict[str, Any]) -> list[str]: | |
| missing = [field for field in REQUIRED_FIELDS if field not in data] | |
| return missing | |
| def _patch_record( | |
| data: dict[str, Any], old_dataset: str, new_dataset: str | |
| ) -> tuple[dict[str, Any], Counter]: | |
| stats: Counter = Counter() | |
| if "hf_username" not in data: | |
| data["hf_username"] = "" | |
| stats["add_hf_username"] += 1 | |
| image_value = data.get("image") | |
| image_file_value = data.get("image_file") | |
| new_base = f"https://huggingface.co/datasets/{new_dataset}/resolve/main/" | |
| if isinstance(image_file_value, str) and old_dataset in image_file_value: | |
| data["image_file"] = image_file_value.replace(old_dataset, new_dataset) | |
| stats["rewrite_image_file"] += 1 | |
| elif ( | |
| not isinstance(image_file_value, str) | |
| and isinstance(image_value, str) | |
| and image_value.strip() | |
| ): | |
| data["image_file"] = new_base + image_value.lstrip("/") | |
| stats["create_image_file"] += 1 | |
| return data, stats | |
| def _write_json(path: Path, data: dict[str, Any]) -> None: | |
| path.write_text( | |
| json.dumps(data, indent=2, ensure_ascii=False) + "\n", | |
| encoding="utf-8", | |
| ) | |
| def main() -> int: | |
| parser = argparse.ArgumentParser( | |
| description="Migrate v0 dataset JSON files to wonders_v2-compatible format." | |
| ) | |
| parser.add_argument( | |
| "--source-dir", | |
| default="./v0_data", | |
| help="Directory containing downloaded v0 dataset files.", | |
| ) | |
| parser.add_argument( | |
| "--old-dataset", | |
| required=True, | |
| help="Old HF dataset repo id currently referenced in image_file URLs.", | |
| ) | |
| parser.add_argument( | |
| "--new-dataset", | |
| required=True, | |
| help="New HF dataset repo id to point image_file URLs to.", | |
| ) | |
| parser.add_argument( | |
| "--dry-run", | |
| action="store_true", | |
| help="Analyze and report changes without writing files.", | |
| ) | |
| parser.add_argument( | |
| "--max-issues", | |
| type=int, | |
| default=30, | |
| help="Maximum number of validation issues to print.", | |
| ) | |
| args = parser.parse_args() | |
| source_dir = Path(args.source_dir).resolve() | |
| if not source_dir.exists(): | |
| print(f"Source directory does not exist: {source_dir}") | |
| return 2 | |
| json_files = _iter_json_files(source_dir) | |
| if not json_files: | |
| print(f"No JSON files found under: {source_dir}") | |
| return 2 | |
| aggregate = Counter() | |
| issues: list[str] = [] | |
| for path in json_files: | |
| try: | |
| data = json.loads(path.read_text(encoding="utf-8")) | |
| except Exception as exc: # noqa: BLE001 | |
| issues.append(f"{path}: invalid JSON ({exc})") | |
| continue | |
| if not isinstance(data, dict): | |
| issues.append(f"{path}: top-level JSON is not an object") | |
| continue | |
| original = json.dumps(data, sort_keys=True, ensure_ascii=False) | |
| data, stats = _patch_record(data, args.old_dataset, args.new_dataset) | |
| aggregate.update(stats) | |
| missing = _validate_record(data) | |
| if missing: | |
| issues.append(f"{path}: missing required fields: {', '.join(missing)}") | |
| patched = json.dumps(data, sort_keys=True, ensure_ascii=False) | |
| if patched != original: | |
| aggregate["changed_files"] += 1 | |
| if not args.dry_run: | |
| _write_json(path, data) | |
| aggregate["processed_json"] = len(json_files) | |
| aggregate["validation_issues"] = len(issues) | |
| print("Migration summary") | |
| print(f"- Source dir: {source_dir}") | |
| print(f"- Processed JSON files: {aggregate['processed_json']}") | |
| print(f"- Changed files: {aggregate['changed_files']}") | |
| print(f"- Added hf_username: {aggregate['add_hf_username']}") | |
| print(f"- Rewritten image_file URLs: {aggregate['rewrite_image_file']}") | |
| print(f"- Created missing image_file: {aggregate['create_image_file']}") | |
| print(f"- Validation issues: {aggregate['validation_issues']}") | |
| if issues: | |
| print("\nValidation issues (showing up to max-issues):") | |
| for issue in issues[: args.max_issues]: | |
| print(f"- {issue}") | |
| if len(issues) > args.max_issues: | |
| print(f"... and {len(issues) - args.max_issues} more") | |
| return 1 | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |