This skill covers working with the TOM wrapper in Semantic Link Labs for programmatic semantic model management.
When to Use This Skill
Use this skill when you need to:
- Read or modify semantic model metadata
- Add/remove tables, columns, measures, relationships
- Work with calculation groups and items
- Manage partitions and data sources
- Implement model management features
Overview
The TOM wrapper provides a Pythonic interface to the Tabular Object Model (TOM), which is the object model for Analysis Services semantic models.
Key Files
| File | Purpose |
|---|---|
src/sempy_labs/tom/__init__.py |
Module exports |
src/sempy_labs/tom/_model.py |
TOMWrapper class implementation |
Basic Usage
Connecting to a Semantic Model
from sempy_labs.tom import connect_semantic_model
# Read-only connection
with connect_semantic_model(
dataset="My Semantic Model",
readonly=True,
workspace="My Workspace"
) as tom:
# Read model metadata
for measure in tom.all_measures():
print(f"Measure: {measure.Name}")
# Read-write connection (requires XMLA read/write enabled)
with connect_semantic_model(
dataset="My Semantic Model",
readonly=False,
workspace="My Workspace"
) as tom:
# Modify model
measure = tom.model.Tables["Sales"].Measures["Revenue"]
measure.Description = "Total revenue in USD"
# Changes are saved when context exits
Important Notes
- XMLA Endpoint: Read-write operations require XMLA read/write enabled on the capacity
- Context Manager: Always use
withstatement - changes are saved on exit - Service Principal: Supported via
service_principal_authenticationcontext
TOMWrapper Properties and Methods
Core Properties
with connect_semantic_model(dataset, workspace=workspace) as tom:
# Access the model
model = tom.model
# Get dataset info
dataset_id = tom._dataset_id
dataset_name = tom._dataset_name
workspace_id = tom._workspace_id
workspace_name = tom._workspace_name
# Check compatibility level
compat_level = tom._compat_level
Iterator Methods
| Method | Returns |
|---|---|
all_columns() |
All columns in all tables |
all_calculated_columns() |
All calculated columns |
all_calculated_tables() |
All calculated tables |
all_calculation_groups() |
All calculation groups |
all_measures() |
All measures |
all_partitions() |
All partitions |
all_hierarchies() |
All hierarchies |
all_levels() |
All hierarchy levels |
all_calculation_items() |
All calculation items |
all_functions() |
All user-defined functions |
Example: Iterating Objects
with connect_semantic_model(dataset, workspace=workspace) as tom:
# List all measures
for measure in tom.all_measures():
print(f"Table: {measure.Parent.Name}, Measure: {measure.Name}")
# List all columns
for column in tom.all_columns():
print(f"Table: {column.Table.Name}, Column: {column.Name}, Type: {column.DataType}")
# List all partitions
for partition in tom.all_partitions():
print(f"Table: {partition.Table.Name}, Partition: {partition.Name}")
Reading Model Metadata
Tables
with connect_semantic_model(dataset, workspace=workspace) as tom:
for table in tom.model.Tables:
print(f"Table: {table.Name}")
print(f" Description: {table.Description}")
print(f" Is Hidden: {table.IsHidden}")
Columns
with connect_semantic_model(dataset, workspace=workspace) as tom:
for column in tom.all_columns():
print(f"Column: {column.Name}")
print(f" Table: {column.Table.Name}")
print(f" Data Type: {column.DataType}")
print(f" Is Hidden: {column.IsHidden}")
Measures
with connect_semantic_model(dataset, workspace=workspace) as tom:
for measure in tom.all_measures():
print(f"Measure: {measure.Name}")
print(f" Table: {measure.Parent.Name}")
print(f" Expression: {measure.Expression}")
print(f" Format String: {measure.FormatString}")
Relationships
with connect_semantic_model(dataset, workspace=workspace) as tom:
for rel in tom.model.Relationships:
print(f"From: {rel.FromTable.Name}[{rel.FromColumn.Name}]")
print(f"To: {rel.ToTable.Name}[{rel.ToColumn.Name}]")
print(f"Cross Filter: {rel.CrossFilteringBehavior}")
Modifying Model Metadata
Update Measure Properties
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
measure = tom.model.Tables["Sales"].Measures["Revenue"]
measure.Description = "Total revenue"
measure.FormatString = "$#,##0.00"
measure.DisplayFolder = "Financial Metrics"
Update Column Properties
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
column = tom.model.Tables["Date"].Columns["Month"]
column.IsHidden = False
column.Description = "Calendar month"
column.SortByColumn = tom.model.Tables["Date"].Columns["MonthNumber"]
Update Table Properties
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
table = tom.model.Tables["Sales"]
table.Description = "Sales transactions"
table.IsHidden = False
Adding Model Objects
Add a Measure
import Microsoft.AnalysisServices.Tabular as TOM
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
table = tom.model.Tables["Sales"]
new_measure = TOM.Measure()
new_measure.Name = "Total Sales"
new_measure.Expression = "SUM(Sales[Amount])"
new_measure.FormatString = "$#,##0.00"
new_measure.Description = "Sum of all sales amounts"
table.Measures.Add(new_measure)
Add a Calculated Column
import Microsoft.AnalysisServices.Tabular as TOM
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
table = tom.model.Tables["Products"]
calc_column = TOM.Column()
calc_column.Name = "Profit Margin"
calc_column.Type = TOM.ColumnType.Calculated
calc_column.Expression = "[Revenue] - [Cost]"
calc_column.DataType = TOM.DataType.Decimal
table.Columns.Add(calc_column)
Add a Relationship
import Microsoft.AnalysisServices.Tabular as TOM
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
from_column = tom.model.Tables["Sales"].Columns["ProductKey"]
to_column = tom.model.Tables["Products"].Columns["ProductKey"]
relationship = TOM.SingleColumnRelationship()
relationship.Name = f"{from_column.Table.Name}_{to_column.Table.Name}"
relationship.FromColumn = from_column
relationship.ToColumn = to_column
relationship.CrossFilteringBehavior = TOM.CrossFilteringBehavior.OneDirection
tom.model.Relationships.Add(relationship)
TOMWrapper Helper Methods
The TOMWrapper class includes many helper methods for common operations:
Display Methods (return DataFrames)
with connect_semantic_model(dataset, workspace=workspace) as tom:
# List all measures
df = tom.list_measures()
# List all columns
df = tom.list_columns()
# List all relationships
df = tom.list_relationships()
# List all partitions
df = tom.list_partitions()
Add Methods
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
# Add measure
tom.add_measure(
table_name="Sales",
measure_name="Total Sales",
expression="SUM(Sales[Amount])",
format_string="$#,##0.00"
)
# Add calculated column
tom.add_calculated_column(
table_name="Products",
column_name="Margin",
expression="[Revenue] - [Cost]"
)
Update Methods
with connect_semantic_model(dataset, readonly=False, workspace=workspace) as tom:
# Update measure expression
tom.update_measure(
table_name="Sales",
measure_name="Total Sales",
expression="SUMX(Sales, Sales[Quantity] * Sales[Price])"
)
Service Principal Authentication
from sempy_labs import service_principal_authentication
from sempy_labs.tom import connect_semantic_model
# Using Service Principal
with service_principal_authentication(
tenant_id="...",
client_id="...",
client_secret="..."
):
with connect_semantic_model(
dataset="My Model",
readonly=False,
workspace="My Workspace"
) as tom:
# Operations use Service Principal credentials
for measure in tom.all_measures():
print(measure.Name)
Azure Analysis Services Support
The TOMWrapper also supports Azure Analysis Services:
with connect_semantic_model(
dataset="MyDatabase",
workspace="asazure://westus2.asazure.windows.net/myserver",
readonly=True
) as tom:
for table in tom.model.Tables:
print(table.Name)
Best Practices
Do's
- ✅ Always use context manager (
withstatement) - ✅ Use
readonly=Truewhen only reading metadata - ✅ Use iterator methods (
all_measures(), etc.) for efficient traversal - ✅ Save changes by exiting the context normally
Don'ts
- ❌ Don't open read-write connections when only reading
- ❌ Don't forget to handle exceptions (changes won't save on exception)
- ❌ Don't hold connections open longer than necessary
Error Handling
from sempy.fabric.exceptions import FabricHTTPException
try:
with connect_semantic_model(
dataset="My Model",
readonly=False,
workspace="My Workspace"
) as tom:
# Modifications...
pass
except FabricHTTPException as e:
print(f"API error: {e}")
except Exception as e:
print(f"Error: {e}")
# Changes NOT saved due to exception
Additional Resources
| Resource | URL |
|---|---|
| TOM Reference | Microsoft Docs |
| Sample Notebook | Tabular Object Model.ipynb |
| API Documentation | ReadTheDocs |