Forum Discussion
Checking if semantic model is composite
Hi,
Do you know how can I check semantic model metadata using sempy? I want to list all composite models inside all workspaces.
kinsin5 As far as I know, there isn’t a first-class “IsComposite” flag in SemPy right now; you infer it from things like partition storage modes and data source types.
Below is a pattern that might work in Fabric notebooks.
1. Basic semantic model metadata with SemPy
From a Fabric notebook:
%pip install semantic-link -q %load_ext sempy import sempy.fabric as fabricTypical metadata calls for a single semantic model:
dataset = "My Semantic Model" workspace = "My Workspace" # or workspace ID # Tables / columns / measures tables = fabric.list_tables(dataset=dataset, workspace=workspace) columns = fabric.list_columns(dataset=dataset, workspace=workspace) measures = fabric.list_measures(dataset=dataset, workspace=workspace) # Relationships relationships = fabric.list_relationships(dataset=dataset, workspace=workspace) # Partitions (this is where storage mode lives) partitions = fabric.list_partitions(dataset=dataset, workspace=workspace, extended=True) # Data sources (for checking Analysis Services / other PBI models, etc.) datasources = fabric.list_datasources(dataset=dataset, workspace=workspace)These all come back as pandas DataFrames, so you can query them however you like.
2. Listing all composite models across all Fabric workspaces
A semantic model is “composite” if, for example:
It has different storage modes across tables/partitions (Import + DirectQuery, DirectLake + Import, etc.), or
It uses another semantic model / AAS as a data source (via AnalysisServices / PowerBI source types).
SemPy gives you enough to derive that:
fabric.list_workspaces() -> all workspaces you can see
fabric.list_datasets(workspace=...) -> semantic models in a workspace
fabric.list_partitions(...) + fabric.list_datasources(...) -> storage modes & source types per model
So maybe something along these lines:
%load_ext sempy import sempy.fabric as fabric import pandas as pd def classify_composite_for_dataset(workspace_name: str, dataset_name: str) -> dict: # Partitions: storage mode per table parts = fabric.list_partitions( workspace=workspace_name, dataset=dataset_name, extended=True ) if parts is None or parts.empty: modes = set() else: modes = set(parts["Mode"].dropna().unique()) # Data sources: where the data is coming from try: ds_df = fabric.list_datasources( workspace=workspace_name, dataset=dataset_name ) except Exception: ds_df = pd.DataFrame() if ds_df is None or ds_df.empty: source_types = set() else: # Column name is typically "Type" in list_datasources output source_types = set(ds_df["Type"].dropna().unique()) # Heuristics for "composite" has_import = "Import" in modes has_directquery = "DirectQuery" in modes has_directlake = "DirectLake" in modes has_multiple_modes = len(modes) > 1 # Remote semantic models / AAS as datasources uses_remote_semantic = any( t in source_types for t in ["AnalysisServices", "PowerBI"] ) is_composite = ( has_multiple_modes or (has_directquery and (modes - {"DirectQuery"})) # DQ + something else or (has_directlake and (has_import or "Dual" in modes)) or uses_remote_semantic ) return { "Workspace": workspace_name, "Dataset": dataset_name, "Modes": ", ".join(sorted(modes)) if modes else "", "SourceTypes": ", ".join(sorted(source_types)) if source_types else "", "IsComposite": is_composite, } def list_composite_models(include_personal_workspaces: bool = False) -> pd.DataFrame: workspaces = fabric.list_workspaces() # all workspaces you can access # Optional: filter out personal workspaces if the column exists if not include_personal_workspaces and "Type" in workspaces.columns: workspaces = workspaces[workspaces["Type"] != "Personal"] rows = [] for _, ws in workspaces.iterrows(): ws_name = ws["Name"] # could also use ws["Id"]; sempy accepts name or ID in most functions datasets = fabric.list_datasets(workspace=ws_name) if datasets is None or datasets.empty: continue for _, ds in datasets.iterrows(): ds_name = ds["Name"] info = classify_composite_for_dataset(ws_name, ds_name) rows.append(info) return pd.DataFrame(rows) # Run the scan all_models = list_composite_models(include_personal_workspaces=False) # Only composite semantic models composite_models = all_models[all_models["IsComposite"]].copy() display(composite_models)That composite_models DataFrame will give you, per semantic model:
Workspace name
Dataset / semantic model name
All storage modes it uses (from list_partitions)
All data source types (from list_datasources)
A boolean IsComposite based on the rules above
You can then:
Write it to a Lakehouse table and build a governance semantic model on top,
Or slice and dice it directly in the notebook.
Notes:
Permissions: You’ll only see workspaces and semantic models you have rights to. For full-tenant scanning you typically combine this with admin or scanner APIs (often via Semantic Link Labs, not plain SemPy).
Definition of “composite”: If you want to be super strict (e.g., “DQ over PBI dataset + local tables only”), tweak the heuristic in classify_composite_for_dataset.
Performance: On big tenants, you may want to:
Filter list_workspaces() first (e.g., only dedicated capacities, only certain domains), and/or
Parallelize per-workspace scans with multiprocessing in Python.
4 Replies
- Nabha-AhmedSuper User
Hi kinsin5
Step 1: Install sempy (if not already installed)
pip install sempy
---Step 2: Connect to Power BI Service
from sempy import Workspace
# Connect to all workspaces
workspaces = Workspace.list_workspaces()
---Step 3: Loop through workspaces and datasets
for ws in workspaces:
print(f"Workspace: {ws.name}")
datasets = ws.datasets # List all datasets in the workspace
for ds in datasets:
# Check if the dataset is a composite model
if ds.is_composite:
print(f" Composite Model: {ds.name}")Explanation:
Workspace.list_workspaces() → gets all workspaces you have access to.
ws.datasets → lists all datasets inside the workspace.
ds.is_composite → identifies composite models (direct query + import, etc.).
---
Step 4: Optional – Export results to CSV
import csv
results = []
for ws in workspaces:
for ds in ws.datasets:
if ds.is_composite:
results.append([ws.name, ds.name])# Save to CSV
with open("composite_models.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Workspace", "Dataset"])
writer.writerows(results)If this solution helped you, please mark it as the accepted answer so it can help others as well.
- v-hjannapuCommunity Support
Hi kinsin5,
I would also take a moment to thank GeraldGEmerick , for actively participating in the community forum and for the solutions you’ve been sharing in the community forum. Your contributions make a real difference.
I wanted to check if you had the opportunity to review the information provided. Please feel free to contact us if you have any further questions.
Regards,
Community Support Team.- v-hjannapuCommunity Support
Hi kinsin5,
I hope the information provided above assists you in resolving the issue. If you have any additional questions or concerns, please do not hesitate to contact us. We are here to support you and will be happy to help with any further assistance you may need.
Regards,
Community Support Team.
- GeraldGEmerickSuper User
kinsin5 As far as I know, there isn’t a first-class “IsComposite” flag in SemPy right now; you infer it from things like partition storage modes and data source types.
Below is a pattern that might work in Fabric notebooks.
1. Basic semantic model metadata with SemPy
From a Fabric notebook:
%pip install semantic-link -q %load_ext sempy import sempy.fabric as fabricTypical metadata calls for a single semantic model:
dataset = "My Semantic Model" workspace = "My Workspace" # or workspace ID # Tables / columns / measures tables = fabric.list_tables(dataset=dataset, workspace=workspace) columns = fabric.list_columns(dataset=dataset, workspace=workspace) measures = fabric.list_measures(dataset=dataset, workspace=workspace) # Relationships relationships = fabric.list_relationships(dataset=dataset, workspace=workspace) # Partitions (this is where storage mode lives) partitions = fabric.list_partitions(dataset=dataset, workspace=workspace, extended=True) # Data sources (for checking Analysis Services / other PBI models, etc.) datasources = fabric.list_datasources(dataset=dataset, workspace=workspace)These all come back as pandas DataFrames, so you can query them however you like.
2. Listing all composite models across all Fabric workspaces
A semantic model is “composite” if, for example:
It has different storage modes across tables/partitions (Import + DirectQuery, DirectLake + Import, etc.), or
It uses another semantic model / AAS as a data source (via AnalysisServices / PowerBI source types).
SemPy gives you enough to derive that:
fabric.list_workspaces() -> all workspaces you can see
fabric.list_datasets(workspace=...) -> semantic models in a workspace
fabric.list_partitions(...) + fabric.list_datasources(...) -> storage modes & source types per model
So maybe something along these lines:
%load_ext sempy import sempy.fabric as fabric import pandas as pd def classify_composite_for_dataset(workspace_name: str, dataset_name: str) -> dict: # Partitions: storage mode per table parts = fabric.list_partitions( workspace=workspace_name, dataset=dataset_name, extended=True ) if parts is None or parts.empty: modes = set() else: modes = set(parts["Mode"].dropna().unique()) # Data sources: where the data is coming from try: ds_df = fabric.list_datasources( workspace=workspace_name, dataset=dataset_name ) except Exception: ds_df = pd.DataFrame() if ds_df is None or ds_df.empty: source_types = set() else: # Column name is typically "Type" in list_datasources output source_types = set(ds_df["Type"].dropna().unique()) # Heuristics for "composite" has_import = "Import" in modes has_directquery = "DirectQuery" in modes has_directlake = "DirectLake" in modes has_multiple_modes = len(modes) > 1 # Remote semantic models / AAS as datasources uses_remote_semantic = any( t in source_types for t in ["AnalysisServices", "PowerBI"] ) is_composite = ( has_multiple_modes or (has_directquery and (modes - {"DirectQuery"})) # DQ + something else or (has_directlake and (has_import or "Dual" in modes)) or uses_remote_semantic ) return { "Workspace": workspace_name, "Dataset": dataset_name, "Modes": ", ".join(sorted(modes)) if modes else "", "SourceTypes": ", ".join(sorted(source_types)) if source_types else "", "IsComposite": is_composite, } def list_composite_models(include_personal_workspaces: bool = False) -> pd.DataFrame: workspaces = fabric.list_workspaces() # all workspaces you can access # Optional: filter out personal workspaces if the column exists if not include_personal_workspaces and "Type" in workspaces.columns: workspaces = workspaces[workspaces["Type"] != "Personal"] rows = [] for _, ws in workspaces.iterrows(): ws_name = ws["Name"] # could also use ws["Id"]; sempy accepts name or ID in most functions datasets = fabric.list_datasets(workspace=ws_name) if datasets is None or datasets.empty: continue for _, ds in datasets.iterrows(): ds_name = ds["Name"] info = classify_composite_for_dataset(ws_name, ds_name) rows.append(info) return pd.DataFrame(rows) # Run the scan all_models = list_composite_models(include_personal_workspaces=False) # Only composite semantic models composite_models = all_models[all_models["IsComposite"]].copy() display(composite_models)That composite_models DataFrame will give you, per semantic model:
Workspace name
Dataset / semantic model name
All storage modes it uses (from list_partitions)
All data source types (from list_datasources)
A boolean IsComposite based on the rules above
You can then:
Write it to a Lakehouse table and build a governance semantic model on top,
Or slice and dice it directly in the notebook.
Notes:
Permissions: You’ll only see workspaces and semantic models you have rights to. For full-tenant scanning you typically combine this with admin or scanner APIs (often via Semantic Link Labs, not plain SemPy).
Definition of “composite”: If you want to be super strict (e.g., “DQ over PBI dataset + local tables only”), tweak the heuristic in classify_composite_for_dataset.
Performance: On big tenants, you may want to:
Filter list_workspaces() first (e.g., only dedicated capacities, only certain domains), and/or
Parallelize per-workspace scans with multiprocessing in Python.