Initial SQL-only 1C adapter baseline
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user