name: tom-operations description: Guide for working with the TOM (Tabular Object Model) wrapper. Use this when modifying semantic models programmatically.
TOM (Tabular Object Model) Operations
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 |