#!/usr/bin/env python3 """Return agent-facing 1C object metadata for base/effective/extension views.""" from __future__ import annotations import argparse import base64 import json import sys from pathlib import Path from typing import Any sys.path.insert(0, str(Path(__file__).resolve().parent)) from build_1c_metadata_from_resolved_object import build_metadata # noqa: E402 from resolve_1c_object import load_json # noqa: E402 def decode_arg(value: str | None, encoded: str | None) -> str | None: if encoded: return base64.b64decode(encoded).decode("utf-8") return value def ru_types(value_type: dict[str, Any] | None) -> list[str]: if not value_type: return [] presentation = value_type.get("presentation") or {} return list(presentation.get("ru") or value_type.get("types") or []) def canonical_types(value_type: dict[str, Any] | None) -> list[str]: if not value_type: return [] return list(value_type.get("types") or []) def attribute_payload( item: dict[str, Any], *, action: str, origin: dict[str, Any], value_type: dict[str, Any] | None = None, include_storage: bool, extra: dict[str, Any] | None = None, ) -> dict[str, Any]: vt = value_type if value_type is not None else item.get("value_type") payload: dict[str, Any] = { "name": item.get("name"), "synonym": item.get("synonym"), "type": ", ".join(ru_types(vt)), "types": ru_types(vt), "canonical_types": canonical_types(vt), "is_composite": bool((vt or {}).get("is_composite")), "uuid": item.get("uuid"), "origin": origin, "effective_action": action, } if include_storage: payload["storage"] = { "storage_routes": item.get("storage_routes") or [], "physical_columns": item.get("physical_columns") or [], } if extra: payload.update(extra) return payload def effective_attributes(metadata: dict[str, Any], *, include_storage: bool) -> list[dict[str, Any]]: result = [] for item in metadata.get("attributes") or []: source = item.get("source") or "base" extension_name = item.get("extension_name") if source == "extension": origin = {"layer": "extension", "extension": extension_name} action = "added" elif item.get("extension_overrides"): origin = {"layer": "base+extension", "extensions": sorted({row.get("extension_name") for row in item.get("extension_overrides") or [] if row.get("extension_name")})} action = "modified" else: origin = {"layer": "base"} action = "base" result.append(attribute_payload(item, action=action, origin=origin, include_storage=include_storage)) return result def base_attributes(metadata: dict[str, Any], *, include_storage: bool) -> list[dict[str, Any]]: result = [] for item in metadata.get("attributes") or []: if (item.get("source") or "base") != "base": continue vt = item.get("base_value_type") or item.get("value_type") extra = {} if item.get("base_value_type"): extra["effective_type"] = ", ".join(ru_types(item.get("value_type"))) extra["effective_canonical_types"] = canonical_types(item.get("value_type")) result.append( attribute_payload( item, action="base", origin={"layer": "base"}, value_type=vt, include_storage=include_storage, extra=extra, ) ) return result def extension_attributes(metadata: dict[str, Any], *, extension: str, include_storage: bool) -> list[dict[str, Any]]: result = [] for item in metadata.get("attributes") or []: if (item.get("source") == "extension") and item.get("extension_name") == extension: result.append( attribute_payload( item, action="added", origin={"layer": "extension", "extension": extension}, include_storage=include_storage, ) ) for override in item.get("extension_overrides") or []: if override.get("extension_name") != extension: continue result.append( attribute_payload( item, action="modified", origin={"layer": "extension", "extension": extension}, value_type=override.get("value_type") or item.get("value_type"), include_storage=include_storage, extra={ "base_uuid": item.get("uuid"), "overlay_uuid": override.get("uuid"), "base_type": ", ".join(ru_types(item.get("base_value_type") or item.get("value_type"))), "base_canonical_types": canonical_types(item.get("base_value_type") or item.get("value_type")), "object_belonging": override.get("object_belonging"), "extended_configuration_object": override.get("extended_configuration_object"), "source_path": override.get("path"), }, ) ) return result def section_payload(item: dict[str, Any], *, include_storage: bool) -> dict[str, Any]: payload = { "name": item.get("name"), "synonym": item.get("synonym"), "uuid": item.get("uuid"), "origin": {"layer": item.get("source") or "base"}, } if include_storage: payload["storage"] = { "storage_routes": item.get("storage_routes") or [], "physical_tables": item.get("physical_tables") or [], } return payload def build_object_metadata( index: dict[str, Any], *, kind: str, name: str, view: str, extension: str | None, include_storage: bool, ) -> dict[str, Any]: metadata = build_metadata(index, kind=kind, name=name) identity = metadata.get("identity") or {} if view == "effective": attributes = effective_attributes(metadata, include_storage=include_storage) elif view == "base": attributes = base_attributes(metadata, include_storage=include_storage) elif view == "extension": if not extension: raise SystemExit("Use --extension with --view extension.") attributes = extension_attributes(metadata, extension=extension, include_storage=include_storage) else: raise SystemExit(f"Unsupported view: {view}") tabular_sections = [section_payload(item, include_storage=include_storage) for item in metadata.get("tabular_sections") or []] if view == "extension": # Section overlays are not merged yet; keep this explicit instead of # pretending extension-only tabular sections were analyzed. tabular_sections = [] result = { "schema": "onec_object_metadata.v1", "view": view, "extension": extension if view == "extension" else None, "object": { "kind": metadata.get("kind"), "name": identity.get("name"), "synonym": (identity.get("synonyms") or {}).get("ru"), "uuid": identity.get("guid"), }, "attributes": attributes, "tabular_sections": tabular_sections, "effective_metadata": metadata.get("effective_metadata"), "resolution": metadata.get("resolution"), "counts": { "attributes": len(attributes), "tabular_sections": len(tabular_sections), }, "diagnostics": { "source_schema": metadata.get("schema"), "storage_included": include_storage, "extension_section_merge": "not_implemented" if view == "extension" else "base_sections_returned", }, } return result def main() -> int: parser = argparse.ArgumentParser(description="Get agent-facing 1C object metadata.") parser.add_argument("--index", type=Path, required=True) parser.add_argument("--kind") parser.add_argument("--name") parser.add_argument("--kind-b64") parser.add_argument("--name-b64") parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective") parser.add_argument("--extension") parser.add_argument("--include-storage", action="store_true") parser.add_argument("--output", type=Path) args = parser.parse_args() kind = decode_arg(args.kind, args.kind_b64) name = decode_arg(args.name, args.name_b64) if not kind or not name: raise SystemExit("Use --kind/--name or --kind-b64/--name-b64.") result = build_object_metadata( load_json(args.index), kind=kind, name=name, view=args.view, extension=args.extension, include_storage=args.include_storage, ) text = json.dumps(result, ensure_ascii=False, indent=2) + "\n" if args.output: args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(text, encoding="utf-8") print(json.dumps({"output": str(args.output), "counts": result["counts"], "view": result["view"]}, ensure_ascii=False)) else: print(text) return 0 if __name__ == "__main__": raise SystemExit(main())