APM

>Agent Skill

@microsoft/tom-operations

skilldevelopment

Guide for working with the TOM (Tabular Object Model) wrapper. Use this when modifying semantic models programmatically.

pythonapi-designdocumentation
apm::install
$apm install @microsoft/tom-operations
apm::skill.md
---
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

```python
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

1. **XMLA Endpoint**: Read-write operations require XMLA read/write enabled on the capacity
2. **Context Manager**: Always use `with` statement - changes are saved on exit
3. **Service Principal**: Supported via `service_principal_authentication` context

---

## TOMWrapper Properties and Methods

### Core Properties

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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

```python
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)

```python
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

```python
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

```python
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

```python
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:

```python
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 (`with` statement)
- ✅ Use `readonly=True` when 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

```python
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](https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular) |
| Sample Notebook | [Tabular Object Model.ipynb](https://github.com/microsoft/semantic-link-labs/blob/main/notebooks/Tabular%20Object%20Model.ipynb) |
| API Documentation | [ReadTheDocs](https://semantic-link-labs.readthedocs.io/en/stable/sempy_labs.tom.html) |