Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """Verify the local Genblaze/B2 SDK contract before live credentials are entered.""" | |
| from __future__ import annotations | |
| import argparse | |
| import importlib | |
| import importlib.metadata | |
| import inspect | |
| import json | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from typing import Any, Callable | |
| ROOT = Path(__file__).resolve().parents[1] | |
| DEFAULT_JSON = ROOT / "docs" / "assets" / "genblaze-contract-report.json" | |
| DEFAULT_MD = ROOT / "docs" / "assets" / "genblaze-contract-report.md" | |
| SCHEMA = "proofframe.genblaze_contract_check.v1" | |
| MODULE_SYMBOLS = { | |
| "genblaze_core": ["KeyStrategy", "Modality", "ObjectStorageSink", "Pipeline"], | |
| "genblaze_gmicloud": ["GMICloudImageProvider"], | |
| "genblaze_openai": ["DalleProvider"], | |
| "genblaze_s3": ["S3StorageBackend"], | |
| } | |
| PACKAGE_DISTS = { | |
| "genblaze_core": "genblaze-core", | |
| "genblaze_gmicloud": "genblaze-gmicloud", | |
| "genblaze_openai": "genblaze-openai", | |
| "genblaze_s3": "genblaze-s3", | |
| } | |
| def utc_now() -> str: | |
| return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") | |
| def check_item( | |
| check_id: str, | |
| label: str, | |
| ok: bool, | |
| detail: str, | |
| remediation: str = "", | |
| ) -> dict[str, Any]: | |
| return { | |
| "id": check_id, | |
| "label": label, | |
| "ok": ok, | |
| "detail": detail, | |
| "remediation": "" if ok else remediation, | |
| } | |
| def package_version(dist_name: str) -> str | None: | |
| try: | |
| return importlib.metadata.version(dist_name) | |
| except importlib.metadata.PackageNotFoundError: | |
| return None | |
| def param_names(callable_obj: Callable[..., Any]) -> set[str]: | |
| return set(inspect.signature(callable_obj).parameters) | |
| def has_params(callable_obj: Callable[..., Any], expected: set[str]) -> tuple[bool, set[str]]: | |
| names = param_names(callable_obj) | |
| return expected <= names, expected - names | |
| def accepts_param_or_kwargs(callable_obj: Callable[..., Any] | None, param_name: str) -> bool: | |
| if callable_obj is None: | |
| return False | |
| signature = inspect.signature(callable_obj) | |
| if param_name in signature.parameters: | |
| return True | |
| return any(param.kind == inspect.Parameter.VAR_KEYWORD for param in signature.parameters.values()) | |
| def signature_check( | |
| checks: list[dict[str, Any]], | |
| *, | |
| check_id: str, | |
| label: str, | |
| callable_obj: Callable[..., Any] | None, | |
| expected: set[str], | |
| ) -> None: | |
| if callable_obj is None: | |
| checks.append( | |
| check_item( | |
| check_id, | |
| label, | |
| False, | |
| "Callable is unavailable because an earlier import check failed.", | |
| "Install integrations with `pip install -e '.[integrations]'`.", | |
| ) | |
| ) | |
| return | |
| ok, missing = has_params(callable_obj, expected) | |
| checks.append( | |
| check_item( | |
| check_id, | |
| label, | |
| ok, | |
| ( | |
| f"Required params: {sorted(expected)}; " | |
| f"observed params: {sorted(param_names(callable_obj))}." | |
| ), | |
| "Update the ProofFrame adapter or pin compatible Genblaze package versions.", | |
| ) | |
| ) | |
| if missing: | |
| checks[-1]["missing_params"] = sorted(missing) | |
| def import_symbols( | |
| checks: list[dict[str, Any]], | |
| *, | |
| importer: Callable[[str], Any], | |
| ) -> dict[str, Any]: | |
| symbols: dict[str, Any] = {} | |
| for module_name, symbol_names in MODULE_SYMBOLS.items(): | |
| try: | |
| module = importer(module_name) | |
| except Exception as exc: | |
| checks.append( | |
| check_item( | |
| f"{module_name}_import", | |
| f"{module_name} import", | |
| False, | |
| f"{module_name} is not importable: {type(exc).__name__}.", | |
| "Install integrations with `pip install -e '.[integrations]'`.", | |
| ) | |
| ) | |
| continue | |
| checks.append( | |
| check_item( | |
| f"{module_name}_import", | |
| f"{module_name} import", | |
| True, | |
| f"{module_name} is importable.", | |
| ) | |
| ) | |
| for symbol_name in symbol_names: | |
| key = f"{module_name}.{symbol_name}" | |
| symbol = getattr(module, symbol_name, None) | |
| symbols[key] = symbol | |
| checks.append( | |
| check_item( | |
| f"{module_name}_{symbol_name}", | |
| f"{key} symbol", | |
| symbol is not None, | |
| f"{key} {'is present' if symbol is not None else 'is missing'}.", | |
| "Update installed Genblaze packages or ProofFrame adapter imports.", | |
| ) | |
| ) | |
| return symbols | |
| def build_report( | |
| *, | |
| importer: Callable[[str], Any] = importlib.import_module, | |
| ) -> dict[str, Any]: | |
| checks: list[dict[str, Any]] = [] | |
| symbols = import_symbols(checks, importer=importer) | |
| pipeline_cls = symbols.get("genblaze_core.Pipeline") | |
| gmicloud_provider_cls = symbols.get("genblaze_gmicloud.GMICloudImageProvider") | |
| openai_provider_cls = symbols.get("genblaze_openai.DalleProvider") | |
| object_storage_sink_cls = symbols.get("genblaze_core.ObjectStorageSink") | |
| s3_backend_cls = symbols.get("genblaze_s3.S3StorageBackend") | |
| key_strategy_cls = symbols.get("genblaze_core.KeyStrategy") | |
| signature_check( | |
| checks, | |
| check_id="gmicloud_image_provider_ctor", | |
| label="GMICloudImageProvider constructor accepts ProofFrame kwargs", | |
| callable_obj=gmicloud_provider_cls, | |
| expected={"api_key", "base_url", "http_timeout"}, | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="openai_image_provider_ctor", | |
| label="DalleProvider constructor accepts ProofFrame kwargs", | |
| callable_obj=openai_provider_cls, | |
| expected={"api_key", "http_timeout"}, | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="pipeline_ctor", | |
| label="Pipeline constructor accepts project_id", | |
| callable_obj=pipeline_cls, | |
| expected={"project_id"}, | |
| ) | |
| if pipeline_cls is not None: | |
| observed_methods = {name for name in {"run", "step"} if callable(getattr(pipeline_cls, name, None))} | |
| checks.append( | |
| check_item( | |
| "pipeline_methods_inspectable", | |
| "Pipeline exposes inspectable step/run methods", | |
| observed_methods == {"run", "step"}, | |
| f"Required methods: ['run', 'step']; observed: {sorted(observed_methods)}.", | |
| "Update the ProofFrame adapter or pin a compatible genblaze-core version.", | |
| ) | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="pipeline_step_signature", | |
| label="Pipeline.step accepts media generation params", | |
| callable_obj=getattr(pipeline_cls, "step", None), | |
| expected={"provider", "model", "prompt", "modality"}, | |
| ) | |
| pipeline_step = getattr(pipeline_cls, "step", None) | |
| checks.append( | |
| check_item( | |
| "pipeline_step_aspect_ratio", | |
| "Pipeline.step accepts aspect_ratio directly or through kwargs", | |
| accepts_param_or_kwargs(pipeline_step, "aspect_ratio"), | |
| "Pipeline.step must accept ProofFrame's aspect_ratio generation parameter directly or through **kwargs.", | |
| "Update the ProofFrame adapter or pin a compatible genblaze-core version.", | |
| ) | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="pipeline_run_signature", | |
| label="Pipeline.run accepts sink and live-proof controls", | |
| callable_obj=getattr(pipeline_cls, "run", None), | |
| expected={"sink", "timeout", "max_retries", "raise_on_failure"}, | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="s3_for_backblaze_signature", | |
| label="S3StorageBackend.for_backblaze accepts B2 credential fields", | |
| callable_obj=getattr(s3_backend_cls, "for_backblaze", None), | |
| expected={"bucket", "region", "key_id", "app_key", "public_url_base", "preflight"}, | |
| ) | |
| signature_check( | |
| checks, | |
| check_id="object_storage_sink_signature", | |
| label="ObjectStorageSink accepts ProofFrame sink settings", | |
| callable_obj=object_storage_sink_cls, | |
| expected={"backend", "prefix", "key_strategy"}, | |
| ) | |
| if s3_backend_cls is None: | |
| checks.append( | |
| check_item( | |
| "s3_readback_methods", | |
| "S3 backend exposes private-bucket readback helpers", | |
| False, | |
| "S3StorageBackend is unavailable.", | |
| "Install genblaze-s3.", | |
| ) | |
| ) | |
| else: | |
| required_methods = {"close", "get", "key_from_url"} | |
| observed_methods = {name for name in required_methods if callable(getattr(s3_backend_cls, name, None))} | |
| checks.append( | |
| check_item( | |
| "s3_readback_methods", | |
| "S3 backend exposes private-bucket readback helpers", | |
| observed_methods == required_methods, | |
| f"Required methods: {sorted(required_methods)}; observed: {sorted(observed_methods)}.", | |
| "Update ProofFrame readback logic or pin a compatible genblaze-s3 version.", | |
| ) | |
| ) | |
| if key_strategy_cls is None: | |
| checks.append( | |
| check_item( | |
| "key_strategy_hierarchical", | |
| "KeyStrategy.HIERARCHICAL is available", | |
| False, | |
| "KeyStrategy is unavailable.", | |
| "Install genblaze-core.", | |
| ) | |
| ) | |
| else: | |
| checks.append( | |
| check_item( | |
| "key_strategy_hierarchical", | |
| "KeyStrategy.HIERARCHICAL is available", | |
| hasattr(key_strategy_cls, "HIERARCHICAL"), | |
| "KeyStrategy exposes HIERARCHICAL for per-campaign Genblaze B2 keys.", | |
| "Update ProofFrame sink key strategy or pin a compatible genblaze-core version.", | |
| ) | |
| ) | |
| ok = all(check["ok"] for check in checks) | |
| return { | |
| "schema": SCHEMA, | |
| "created_at": utc_now(), | |
| "ok": ok, | |
| "mode": "sdk_contract_ready" if ok else "sdk_contract_blocked", | |
| "package_versions": { | |
| module_name: package_version(dist_name) | |
| for module_name, dist_name in PACKAGE_DISTS.items() | |
| }, | |
| "checks": checks, | |
| "failed_checks": [check["id"] for check in checks if not check["ok"]], | |
| "secret_policy": ( | |
| "This report reads Python package metadata and callable signatures only. " | |
| "It does not read environment variables, credential files, provider responses, " | |
| "Backblaze keys, Genblaze provider keys, cookies, or signed URLs." | |
| ), | |
| } | |
| def write_outputs(report: dict[str, Any], json_path: Path, markdown_path: Path) -> None: | |
| json_path.parent.mkdir(parents=True, exist_ok=True) | |
| markdown_path.parent.mkdir(parents=True, exist_ok=True) | |
| json_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") | |
| markdown_path.write_text(render_markdown(report), encoding="utf-8") | |
| def render_markdown(report: dict[str, Any]) -> str: | |
| lines = [ | |
| "# Genblaze SDK Contract Check", | |
| "", | |
| f"Mode: `{report['mode']}`", | |
| f"OK: `{str(report['ok']).lower()}`", | |
| f"Created: `{report['created_at']}`", | |
| "", | |
| "## Package Versions", | |
| "", | |
| ] | |
| for module_name, version in report["package_versions"].items(): | |
| lines.append(f"- `{module_name}`: `{version or 'missing'}`") | |
| lines.extend(["", "## Checks", ""]) | |
| for check in report["checks"]: | |
| status = "OK" if check["ok"] else "FAIL" | |
| lines.append(f"- {status} `{check['id']}`: {check['detail']}") | |
| if check.get("remediation"): | |
| lines.append(f" Remediation: {check['remediation']}") | |
| lines.extend(["", "## Secret Policy", "", report["secret_policy"], ""]) | |
| return "\n".join(lines) | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Verify the local Genblaze/B2 SDK contract.") | |
| parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON) | |
| parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MD) | |
| return parser | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| report = build_report() | |
| write_outputs(report, args.json_out, args.markdown_out) | |
| print( | |
| json.dumps( | |
| { | |
| "ok": report["ok"], | |
| "mode": report["mode"], | |
| "json": str(args.json_out), | |
| "markdown": str(args.markdown_out), | |
| "failed_checks": report["failed_checks"], | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| raise SystemExit(0 if report["ok"] else 2) | |
| if __name__ == "__main__": | |
| main() | |