Forum Discussion
Checking if semantic model is composite
- 9 months ago
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.
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 fabric
Typical 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.