Unlock a world of possibilities! Login now and discover the exclusive benefits awaiting you.
Hello,
Following is the script which we are using to fetch the object metadata from qlik cloud dashboard. We want to list out the dimension, measures and their underlying definitions. However when we run the script we do not the expression used for the measure. We only get label of the measure not the underlying expression.
Example -
Let's suppose there is a bar chart on qlik cloud dashboard which has month as dimension and sum(sales) as measure. We want the output like -
Dimension - Month, Expression - Sum(Sales)
Script -
import websocket
import json
import ssl
import os
from datetime import datetime
# Assuming config.py exists and contains TENANT_URL, API_KEY, APP_ID
from config import TENANT_URL, API_KEY, APP_ID
TENANT = TENANT_URL.replace("https://", "").rstrip("/")
WS_URL = f"wss://{TENANT}/app/{APP_ID}"
headers = {
"Authorization": f"Bearer {API_KEY}"
}
state = {
"doc_handle": None,
"infos": [],
"current_index": 0,
"object_handles": {},
"output_data": []
}
def on_message(ws, message):
response = json.loads(message)
print(response)
if response.get("id") == 1:
state["doc_handle"] = response["result"]["qReturn"]["qHandle"]
ws.send(json.dumps({
"jsonrpc": "2.0",
"id": 2,
"handle": state["doc_handle"],
"method": "GetAllInfos",
"params": {}
}))
elif response.get("id") == 2:
state["infos"] = response["result"]["qInfos"]
print(f"\n[✓] Found {len(state['infos'])} objects in the app\n")
request_next_object(ws)
elif response.get("id", 0) >= 1000 and "qReturn" in response.get("result", {}):
qid = response["id"] - 1000
obj_handle = response["result"]["qReturn"]["qHandle"]
state["object_handles"][qid] = obj_handle
ws.send(json.dumps({
"jsonrpc": "2.0",
"id": 2000 + qid,
"handle": obj_handle,
"method": "GetLayout",
"params": {}
}))
elif response.get("id", 0) >= 2000:
qid = response["id"] - 2000
layout = response.get("result", {}).get("qLayout", {})
metadata = extract_metadata(state["infos"][qid], layout)
print_metadata(metadata)
state["output_data"].append(metadata)
state["current_index"] += 1
request_next_object(ws)
def request_next_object(ws):
if state["current_index"] < len(state["infos"]):
info = state["infos"][state["current_index"]]
qid = state["current_index"]
obj_id = info["qId"]
ws.send(json.dumps({
"jsonrpc": "2.0",
"id": 1000 + qid,
"handle": state["doc_handle"],
"method": "GetObject",
"params": {
"qId": obj_id
}
}))
else:
print("\n✅ Completed extracting all metadata.")
save_output_to_file()
ws.close()
def extract_metadata(info, layout):
qid = info["qId"]
qtype = info["qType"]
title = layout.get("title") or layout.get("qMeta", {}).get("title", "N/A")
metadata = {
"id": qid,
"type": qtype,
"title": title,
"dimensions": [],
"measures": [],
"expressions": [] # Changed to a list to hold multiple expressions
}
if "qHyperCube" in layout:
dims = layout["qHyperCube"].get("qDimensionInfo", [])
meas = layout["qHyperCube"].get("qMeasureInfo", [])
# Extract dimensions and their expressions
for d in dims:
metadata["dimensions"].append(d.get("qFallbackTitle"))
if d.get("qDef", {}).get("qDef"): # Check for calculated dimension expression
metadata["expressions"].append(d["qDef"]["qDef"])
# Extract measures and their expressions
for m in meas:
metadata["measures"].append(m.get("qFallbackTitle"))
if m.get("qDef", {}).get("qDef"): # Check for measure expression
metadata["expressions"].append(m["qDef"]["qDef"])
elif "qListObject" in layout:
# For listboxes, the dimension definition is often here
dim_info = layout["qListObject"].get("qDimensionInfo", {})
metadata["dimensions"].append(dim_info.get("qFallbackTitle", "N/A"))
if dim_info.get("qDef", {}).get("qDef"): # Check for calculated dimension expression in listbox
metadata["expressions"].append(dim_info["qDef"]["qDef"])
# This might still be relevant for specific text objects or variables
if "qStringExpression" in layout and layout.get("qStringExpression", {}).get("qExpr"):
metadata["expressions"].append(layout["qStringExpression"]["qExpr"])
# If the expressions list is empty, set it to None for consistency with previous structure
# if not metadata["expressions"]:
# metadata["expressions"] = None # Optional: if you prefer None for no expressions
return metadata
def print_metadata(metadata):
print("---")
print(f"ID: {metadata['id']}")
print(f"Type: {metadata['type']}")
print(f"Title: {metadata['title']}")
if metadata["dimensions"]:
print("Dimensions:", metadata["dimensions"])
if metadata["measures"]:
print("Measures:", metadata["measures"])
if metadata["expressions"]: # Changed to expressions
print("Expressions:", metadata["expressions"]) # Changed to expressions
def save_output_to_file(filename="output_metadata.json"):
with open(filename, "w", encoding="utf-8") as f:
json.dump(state["output_data"], f, indent=4)
print(f"\n[✓] Metadata saved to {filename}")
def on_open(ws):
print("[✓] WebSocket connected")
ws.send(json.dumps({
"jsonrpc": "2.0",
"id": 1,
"method": "OpenDoc",
"handle": -1,
"params": [APP_ID]
}))
def on_error(ws, error):
print("Error:", error)
def on_close(ws, code, msg):
print("WebSocket closed")
websocket.enableTrace(False)
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close,
header=headers
)
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
One problem I see is that you are only looking at the qDef property. If the Measure references a Master Measure, then qDef will be empty and qLibraryId will have the id of the Master Measure.
I recommend the excellent free Qlik Explorer for Developers to examine and understand the structure of Qlik Objects.
I note there is a new Python Qlik SDK you may find easier to use than crafting the websocket calls. https://qlik.dev/toolkits/platform-sdk/
-Rob
One problem I see is that you are only looking at the qDef property. If the Measure references a Master Measure, then qDef will be empty and qLibraryId will have the id of the Master Measure.
I recommend the excellent free Qlik Explorer for Developers to examine and understand the structure of Qlik Objects.
I note there is a new Python Qlik SDK you may find easier to use than crafting the websocket calls. https://qlik.dev/toolkits/platform-sdk/
-Rob