214 lines
8.4 KiB
Python
214 lines
8.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from urllib import request
|
|
|
|
|
|
def rpc(adapter_url: str, method: str, payload: dict[str, Any], *, timeout: int = 240) -> dict[str, Any]:
|
|
body = json.dumps({"method": method, "payload": payload}, ensure_ascii=False).encode("utf-8")
|
|
req = request.Request(
|
|
f"{adapter_url.rstrip('/')}/rpc",
|
|
data=body,
|
|
headers={"Content-Type": "application/json; charset=utf-8"},
|
|
method="POST",
|
|
)
|
|
with request.urlopen(req, timeout=timeout) as response:
|
|
return json.loads(response.read().decode("utf-8", errors="replace"))
|
|
|
|
|
|
def extension_template_payloads(
|
|
adapter_url: str,
|
|
*,
|
|
base_id: str,
|
|
extension: str,
|
|
query: str,
|
|
timeout_seconds: int,
|
|
refresh_cache: bool,
|
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
result = rpc(
|
|
adapter_url,
|
|
"extension.objects.find",
|
|
{
|
|
"base_id": base_id,
|
|
"extension": extension,
|
|
"query": query,
|
|
"limit": 500,
|
|
"refresh_cache": refresh_cache,
|
|
"include_storage": True,
|
|
"timeout_seconds": timeout_seconds,
|
|
},
|
|
timeout=max(240, timeout_seconds + 60),
|
|
)
|
|
by_guid: dict[str, dict[str, Any]] = {}
|
|
for item in result.get("objects") or []:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
name = str(item.get("name") or "")
|
|
guid = str(item.get("guid") or "")
|
|
if not name or not guid or guid in by_guid:
|
|
continue
|
|
payload_entry = next(
|
|
(
|
|
entry
|
|
for entry in item.get("manifest_entries") or []
|
|
if isinstance(entry, dict)
|
|
and (entry.get("suffix") == ".0" or str(entry.get("object_id") or "").endswith(".0"))
|
|
and entry.get("cas_key")
|
|
),
|
|
None,
|
|
)
|
|
if payload_entry:
|
|
by_guid[guid] = {
|
|
"name": name,
|
|
"metadata_guid": guid,
|
|
"payload_file_name": payload_entry.get("cas_key"),
|
|
"manifest_entry": payload_entry,
|
|
}
|
|
return result, sorted(by_guid.values(), key=lambda item: str(item.get("name") or ""))
|
|
|
|
|
|
def profile_payload(
|
|
adapter_url: str,
|
|
*,
|
|
base_id: str,
|
|
item: dict[str, Any],
|
|
timeout_seconds: int,
|
|
refresh_cache: bool,
|
|
sample_limit: int,
|
|
) -> dict[str, Any]:
|
|
payload_file_name = str(item.get("payload_file_name") or "")
|
|
result = rpc(
|
|
adapter_url,
|
|
"templates.read",
|
|
{
|
|
"base_id": base_id,
|
|
"kind": "Template",
|
|
"guid": payload_file_name,
|
|
"file_name": payload_file_name,
|
|
"table": "ConfigCAS",
|
|
"sections": "summary,merged_ranges,merges,coordinate_hints,cells,parameters,named_areas,formats,diagnostics",
|
|
"view": "structure",
|
|
"refresh_cache": refresh_cache,
|
|
"max_cells": sample_limit,
|
|
"max_merged": sample_limit,
|
|
"max_parameters": sample_limit,
|
|
"max_named_areas": sample_limit,
|
|
"timeout_seconds": timeout_seconds,
|
|
},
|
|
timeout=max(240, timeout_seconds + 60),
|
|
)
|
|
templates = result.get("templates") or []
|
|
structure = (templates[0].get("structure") if templates and isinstance(templates[0], dict) else {}) or {}
|
|
return {
|
|
**item,
|
|
"status": result.get("status"),
|
|
"counts": structure.get("counts") or {},
|
|
"capacity_dimensions": structure.get("capacity_dimensions"),
|
|
"used_dimensions": structure.get("used_dimensions"),
|
|
"format_dimensions": structure.get("format_dimensions"),
|
|
"capabilities": structure.get("capabilities"),
|
|
"merge_block_counts": [
|
|
candidate.get("count")
|
|
for candidate in structure.get("merge_record_block_candidates") or []
|
|
if isinstance(candidate, dict)
|
|
],
|
|
"merge_count_hint_counts": [
|
|
hint.get("count")
|
|
for hint in structure.get("merge_count_hints") or []
|
|
if isinstance(hint, dict)
|
|
],
|
|
"sample_merged_ranges": structure.get("merged_ranges") or [],
|
|
"sample_merge_count_hints": structure.get("merge_count_hints") or [],
|
|
"sample_merge_record_block_candidates": structure.get("merge_record_block_candidates") or [],
|
|
"sample_cell_coordinate_hints": structure.get("cell_coordinate_hints") or [],
|
|
"sample_cells": structure.get("cells") or [],
|
|
"sample_cell_parameters": structure.get("cell_parameters") or [],
|
|
"sample_named_ranges": structure.get("named_range_candidates") or [],
|
|
"sample_column_widths": structure.get("column_widths") or [],
|
|
"sample_format_table": structure.get("format_table") or [],
|
|
"sample_font_table": structure.get("font_table") or [],
|
|
"sample_format_style_index_table": structure.get("format_style_index_table") or {},
|
|
"sample_cell_format_links": structure.get("cell_format_links") or [],
|
|
"cell_format_link_stats": structure.get("cell_format_link_stats") or {},
|
|
}
|
|
|
|
|
|
def render_markdown(payload: dict[str, Any]) -> str:
|
|
lines = [
|
|
"# Extension SQL direct template profiles",
|
|
"",
|
|
f"- Base: `{payload.get('base_id')}`",
|
|
f"- Extension: `{payload.get('extension')}`",
|
|
f"- Profiles: `{(payload.get('counts') or {}).get('profiles')}`",
|
|
"",
|
|
"| Template | Cells | Cell params | Used | Merged ranges | Merge blocks | Merge count hints |",
|
|
"| --- | ---: | ---: | --- | ---: | --- | --- |",
|
|
]
|
|
for item in payload.get("profiles") or []:
|
|
counts = item.get("counts") if isinstance(item.get("counts"), dict) else {}
|
|
used = item.get("used_dimensions") if isinstance(item.get("used_dimensions"), dict) else {}
|
|
lines.append(
|
|
f"| `{item.get('name')}` | {counts.get('cells') or 0} | {counts.get('cell_parameters') or 0} | "
|
|
f"`{used.get('rows')}x{used.get('columns')}` | {counts.get('merged_ranges') or 0} | `{item.get('merge_block_counts') or []}` | "
|
|
f"`{item.get('merge_count_hint_counts') or []}` |"
|
|
)
|
|
return "\n".join(lines) + "\n"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Profile extension common template payloads from live SQL ConfigCAS.")
|
|
parser.add_argument("--adapter-url", default="http://docker-gpu.cin.su:8011")
|
|
parser.add_argument("--base-id", default="upo_test")
|
|
parser.add_argument("--extension", required=True)
|
|
parser.add_argument("--query", default="t_MOXEL")
|
|
parser.add_argument("--timeout-seconds", type=int, default=180)
|
|
parser.add_argument("--sample-limit", type=int, default=20)
|
|
parser.add_argument("--refresh-cache", action="store_true")
|
|
parser.add_argument("--output-json", required=True)
|
|
parser.add_argument("--output-markdown", required=True)
|
|
args = parser.parse_args()
|
|
|
|
find_result, payloads = extension_template_payloads(
|
|
args.adapter_url,
|
|
base_id=args.base_id,
|
|
extension=args.extension,
|
|
query=args.query,
|
|
timeout_seconds=int(args.timeout_seconds or 180),
|
|
refresh_cache=bool(args.refresh_cache),
|
|
)
|
|
profiles = [
|
|
profile_payload(
|
|
args.adapter_url,
|
|
base_id=args.base_id,
|
|
item=item,
|
|
timeout_seconds=int(args.timeout_seconds or 180),
|
|
refresh_cache=bool(args.refresh_cache),
|
|
sample_limit=int(args.sample_limit or 20),
|
|
)
|
|
for item in payloads
|
|
]
|
|
payload = {
|
|
"schema": "onec_extension_template_profiles.v1",
|
|
"base_id": args.base_id,
|
|
"extension": args.extension,
|
|
"query": args.query,
|
|
"find_counts": find_result.get("counts"),
|
|
"profiles": profiles,
|
|
"counts": {"profiles": len(profiles)},
|
|
}
|
|
output_json = Path(args.output_json)
|
|
output_markdown = Path(args.output_markdown)
|
|
output_json.parent.mkdir(parents=True, exist_ok=True)
|
|
output_markdown.parent.mkdir(parents=True, exist_ok=True)
|
|
output_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
output_markdown.write_text(render_markdown(payload), encoding="utf-8")
|
|
print(json.dumps({"status": "ok", "json": str(output_json), "markdown": str(output_markdown), "counts": payload["counts"]}, ensure_ascii=False, indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|