Forum Discussion
Notebook with sempy_labs failed when executed in DataPipeline
- 4 months ago
I have implemented a workaround function to bypass editing the XMLA endpoint (directlake.update_direct_lake_model_connection()) using a REST-based approach.
Here is my function to update the direct lake connectiondef update_direct_lake_onelake_connection( dataset_name: str, workspace_name: str, lakehouse_name: str, lakehouse_workspace_name: str, ) -> None: """ Patch the OneLake DFS URL in a Direct Lake semantic model's shared expressions to point to the correct target Lakehouse. Uses the Fabric REST API (getDefinition / updateDefinition) so that Service Principal authentication is honoured correctly — bypassing the XMLA endpoint where SP tokens are not forwarded by sempy_labs. Parameters ---------- dataset_name : Display name of the target semantic model. workspace_name : Display name of the workspace containing the model. lakehouse_name : Display name of the target lakehouse. lakehouse_workspace_name : Display name of the workspace containing the lakehouse. """ logger.info("Resolving IDs for Direct Lake connection update...") workspace_id = get_workspace_id(workspace_name) lakehouse_workspace_id = get_workspace_id(lakehouse_workspace_name) lakehouse_id = get_lakehouse_id(lakehouse_workspace_id, lakehouse_name) dataset_id = get_semantic_model_id(workspace_id, dataset_name) if not dataset_id: raise ValueError(f"Semantic model '{dataset_name}' not found in workspace '{workspace_name}'.") logger.info(f" Workspace ID: {workspace_id}") logger.info(f" Lakehouse Workspace ID: {lakehouse_workspace_id}") logger.info(f" Lakehouse ID: {lakehouse_id}") logger.info(f" Semantic Model ID: {dataset_id}") # -------------------------------------------------- # Get model definition (handles sync 200 and async 202) # -------------------------------------------------- r = requests.post( f"{FABRIC_API_BASE}/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition", headers=HEADERS, ) r.raise_for_status() if r.status_code == 202: poll_result = poll_operation(r.headers["Location"], "getDefinition") if "definition" in poll_result: parts = poll_result["definition"]["parts"] else: result_url = poll_result.get("resourceLocation") or r.headers["Location"] + "/result" result_r = requests.get(result_url, headers=HEADERS) result_r.raise_for_status() parts = result_r.json()["definition"]["parts"] else: parts = r.json()["definition"]["parts"] # -------------------------------------------------- # Patch OneLake URL in all matching parts # -------------------------------------------------- new_url = ( f"https://onelake.dfs.fabric.microsoft.com/" f"{lakehouse_workspace_id}/{lakehouse_id}" ) onelake_pattern = re.compile( r"https://onelake\.dfs\.fabric\.microsoft\.com/" r"[0-9a-fA-F\-]{36}/[0-9a-fA-F\-]{36}" ) patched = False for part in parts: content = base64.b64decode(part["payload"]).decode("utf-8") if "onelake.dfs.fabric.microsoft.com" not in content.lower(): continue updated = onelake_pattern.sub(new_url, content) if updated == content: logger.warning( f"Part '{part['path']}' contains OneLake URL but regex did not match — " f"inspect manually:\n{content}" ) continue logger.info(f" Patched part: {part['path']}") part["payload"] = base64.b64encode(updated.encode("utf-8")).decode("utf-8") patched = True if not patched: raise ValueError( f"No patchable OneLake URL found in model '{dataset_name}'. " "Verify this is a Direct Lake model." ) # -------------------------------------------------- # Push updated definition back (handles sync / async) # -------------------------------------------------- r2 = requests.post( f"{FABRIC_API_BASE}/workspaces/{workspace_id}/semanticModels/{dataset_id}/updateDefinition", headers=HEADERS, json={"definition": {"parts": parts}}, ) r2.raise_for_status() if r2.status_code == 202: poll_operation(r2.headers["Location"], "updateDefinition") logger.info("Direct Lake OneLake connection updated successfully.")
Hi Kuladeep ,
Before confirming the exact cause, could you check a few things in your environment?
1. Capacity XMLA Endpoint
Go to Admin Portal → Capacity Settings → [your capacity] → XMLA Endpoint
Is it set to Read, Read/Write, or disabled?
update_direct_lake_model_connection uses XMLA internally — if it's not set to Read/Write, the Service Principal can't resolve the workspace and throws exactly that error.
2. Service Principal enabled in Tenant Settings
Go to Admin Portal → Tenant Settings → Developer Settings
Is "Allow service principals to use Power BI APIs" enabled? Does it apply to the whole organization or only to a specific Security Group? Is your SP included?
3. SP role in the TARGET workspace
In the destination workspace (6e33da9d...), what role does the Service Principal have assigned? It needs at least Member, ideally Admin.
If my comments helped solve your question, it would be great if you could like all comment and mark it as the accepted solution. It helps others with the same issue and also motivates me to keep contributing.
Thanks a lot, I really appreciate it.
Hi arabalca,
Thanks for your reply.
1. Yes, XMLA Endpoint is set to `Read Write`
2. The Service principal settings enabled on tenant and the security group, which the SP is part of, is also added to the settings
3. The SP has Admin level permissions on the WS
Yet it fail.. Is there any other way to repoint S`M connection to new LH via API or notebookutils?
- Kuladeep4 months ago
Advocate II
I have implemented a workaround function to bypass editing the XMLA endpoint (directlake.update_direct_lake_model_connection()) using a REST-based approach.
Here is my function to update the direct lake connectiondef update_direct_lake_onelake_connection( dataset_name: str, workspace_name: str, lakehouse_name: str, lakehouse_workspace_name: str, ) -> None: """ Patch the OneLake DFS URL in a Direct Lake semantic model's shared expressions to point to the correct target Lakehouse. Uses the Fabric REST API (getDefinition / updateDefinition) so that Service Principal authentication is honoured correctly — bypassing the XMLA endpoint where SP tokens are not forwarded by sempy_labs. Parameters ---------- dataset_name : Display name of the target semantic model. workspace_name : Display name of the workspace containing the model. lakehouse_name : Display name of the target lakehouse. lakehouse_workspace_name : Display name of the workspace containing the lakehouse. """ logger.info("Resolving IDs for Direct Lake connection update...") workspace_id = get_workspace_id(workspace_name) lakehouse_workspace_id = get_workspace_id(lakehouse_workspace_name) lakehouse_id = get_lakehouse_id(lakehouse_workspace_id, lakehouse_name) dataset_id = get_semantic_model_id(workspace_id, dataset_name) if not dataset_id: raise ValueError(f"Semantic model '{dataset_name}' not found in workspace '{workspace_name}'.") logger.info(f" Workspace ID: {workspace_id}") logger.info(f" Lakehouse Workspace ID: {lakehouse_workspace_id}") logger.info(f" Lakehouse ID: {lakehouse_id}") logger.info(f" Semantic Model ID: {dataset_id}") # -------------------------------------------------- # Get model definition (handles sync 200 and async 202) # -------------------------------------------------- r = requests.post( f"{FABRIC_API_BASE}/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition", headers=HEADERS, ) r.raise_for_status() if r.status_code == 202: poll_result = poll_operation(r.headers["Location"], "getDefinition") if "definition" in poll_result: parts = poll_result["definition"]["parts"] else: result_url = poll_result.get("resourceLocation") or r.headers["Location"] + "/result" result_r = requests.get(result_url, headers=HEADERS) result_r.raise_for_status() parts = result_r.json()["definition"]["parts"] else: parts = r.json()["definition"]["parts"] # -------------------------------------------------- # Patch OneLake URL in all matching parts # -------------------------------------------------- new_url = ( f"https://onelake.dfs.fabric.microsoft.com/" f"{lakehouse_workspace_id}/{lakehouse_id}" ) onelake_pattern = re.compile( r"https://onelake\.dfs\.fabric\.microsoft\.com/" r"[0-9a-fA-F\-]{36}/[0-9a-fA-F\-]{36}" ) patched = False for part in parts: content = base64.b64decode(part["payload"]).decode("utf-8") if "onelake.dfs.fabric.microsoft.com" not in content.lower(): continue updated = onelake_pattern.sub(new_url, content) if updated == content: logger.warning( f"Part '{part['path']}' contains OneLake URL but regex did not match — " f"inspect manually:\n{content}" ) continue logger.info(f" Patched part: {part['path']}") part["payload"] = base64.b64encode(updated.encode("utf-8")).decode("utf-8") patched = True if not patched: raise ValueError( f"No patchable OneLake URL found in model '{dataset_name}'. " "Verify this is a Direct Lake model." ) # -------------------------------------------------- # Push updated definition back (handles sync / async) # -------------------------------------------------- r2 = requests.post( f"{FABRIC_API_BASE}/workspaces/{workspace_id}/semanticModels/{dataset_id}/updateDefinition", headers=HEADERS, json={"definition": {"parts": parts}}, ) r2.raise_for_status() if r2.status_code == 202: poll_operation(r2.headers["Location"], "updateDefinition") logger.info("Direct Lake OneLake connection updated successfully.")