#!/usr/bin/env python3 """Check a documented subset of model package fields; never load model code.""" import argparse import json import math import sys from pathlib import Path, PurePosixPath def check_package(directory): root = Path(directory).resolve() errors, warnings = [], [] def require(condition, message): if not condition: errors.append(message) def read(name): path = root / name if not path.resolve().is_relative_to(root): raise ValueError(name + ": path resolves outside the package") value = json.loads(path.read_text(encoding="utf-8")) if not isinstance(value, dict): raise ValueError(name + ": expected a JSON object") return value try: info, categories = read("model_info.json"), read("coco_categories.json") except (OSError, ValueError) as error: return {"errors": [str(error)], "warnings": [], "status": "invalid"} for field in ("model_type", "task_type"): value = info.get(field) require(isinstance(value, str) and bool(value.strip()), field + ": a string is required") if isinstance(value, str) and "<" in value: warnings.append(field + ": training placeholder; replace it with a runtime value") size = info.get("img_size", {}) for axis in ("height", "width"): value = size.get(axis) if isinstance(size, dict) else None require(type(value) is int and value > 0, "img_size." + axis + ": a positive integer is required") deployment = info.get("deploy_profile", {}) profiles = deployment.get("profiles") if isinstance(deployment, dict) else None valid_profiles = isinstance(profiles, list) and bool(profiles) and all(isinstance(p, str) and p.strip() for p in profiles) require(valid_profiles, "deploy_profile.profiles: a nonempty string[] is required") if valid_profiles: require(deployment.get("default_profile") in profiles, "default_profile is absent from profiles") if any("<" in p for p in profiles): warnings.append("profiles: training placeholder; check the target runtime's profiles") weights = info.get("weights_path") require(isinstance(weights, str) and bool(weights), "weights_path: a relative path is required") if isinstance(weights, str) and weights: pure = PurePosixPath(weights) if pure.is_absolute() or ".." in pure.parts or "\\" in weights or ":" in weights: errors.append("weights_path: a path within the package using / is required") elif any(char in weights for char in "*?["): warnings.append("weights_path: some runtimes allow templates; this checker does not expand them") else: path = root / weights require(path.resolve().is_relative_to(root), "weights_path: path resolves outside the package") require(path.is_file(), "weights_path: file not found") items = categories.get("categories") require(isinstance(items, list), "categories: an array is required") ids = set() for index, category in enumerate(items if isinstance(items, list) else []): prefix = "categories[" + str(index) + "]" if not isinstance(category, dict): errors.append(prefix + ": an object is required") continue value = category.get("id") require(type(value) is int, prefix + ".id: an integer is required") if type(value) is int: require(value not in ids, prefix + ".id: duplicate") ids.add(value) for field in ("name", "ru_name", "full_ru_name", "supercategory"): require(isinstance(category.get(field), str), prefix + "." + field + ": a string is required") threshold = category.get("threshold") require(type(threshold) in (int, float) and math.isfinite(threshold), prefix + ".threshold: a finite number is required") color = category.get("color") require(isinstance(color, list) and len(color) == 3 and all(type(c) is int and 0 <= c <= 255 for c in color), prefix + ".color: three integers in 0..255 are required") for field in ("is_violation", "is_primary", "exclude"): require(type(category.get(field)) is bool, prefix + "." + field + ": a boolean is required") warnings.append("Weights, inference, model type, additional files and runtime compatibility have not been checked") return {"errors": errors, "warnings": warnings, "status": "invalid" if errors else "structure_checked"} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("directory", help="extracted model directory") args = parser.parse_args() result = check_package(args.directory) print(json.dumps(result, ensure_ascii=False, indent=2)) return 1 if result["errors"] else 0 if __name__ == "__main__": sys.exit(main())