Formatting All DAX Measures Using Semantic Link In Fabric

If it's not formatted, it's not DAX...

Last year I shared a blog post on formatting DAX expressions using DAX Formatter API (thanks to SQL BI). I extend the same idea in this blog to format all and save the formatted DAX in all measures in a semantic model using semantic link.

💡
WARNING : Since this script will be making changes to the model using XMLA, you will NOT be able to download the semantic model after making the changes, similar to other external tools such as Tabular Editor.

Get A List of Measures

import sempy.fabric as fabric

ws = "Sales Workspace" #workspace name
ds = "FormatDaxTest"  #semantic model name

measures_df = fabric.list_measures(workspace=ws, dataset=ds)
measures_df

Optional: Backup The Measures

Before you modify the measures, I suggest backing it up just as a precaution.

#Mount a lakehouse and provide file api path as shown below
def backup_measures(workspace, dataset, file_api_path):
    return fabric.list_measures(workspace=workspace, dataset=dataset).to_csv(file_api_path)

backup_measures(workspace=ws, dataset=ds, file_api_path="/lakehouse/default/Files/measures.csv")

Format The DAX

Use the DAX Formatter API responsibly. Refer to the official documentation for API details.

import requests
from bs4 import BeautifulSoup

def format_dax(dax_expression=None, measure_df=None, measure_name=None, line='short', region='US'):
    """
    Author: Sandeep Pawar   |    fabric.guru   |   06-13-2024
    You must specify either the DAX for the measure or the measure_df + measure_name
    But not both. 

    Refer to https://www.daxformatter.com/ for DAX Formatter details and options
    """
    if (dax_expression is None and measure_df is None) or (dax_expression is not None and (measure_df is not None and measure_name is not None)):
        return "Error: Provide either a DAX expression or a combination of measure_df and measure_name, but not both."

    if dax_expression is not None:
        dax_to_format = dax_expression
    else:
        if measure_df is not None and measure_name is not None:
            measure_row = measure_df[measure_df['Measure Name'] == measure_name]
            if measure_row.empty:
                return f"Error: Measure '{measure_name}' not found in DataFrame."
            dax_to_format = measure_row.iloc[0]['Measure Expression']
        else:
            return "Error: Provide either a DAX expression or a combination of measure_df and measure_name."

    url = "https://www.daxformatter.com"
    payload = {
        'r': region,
        'fx': dax_to_format,
        'embed': '1',
        's': 'auto',
        'l': line
    }

    try:
        response = requests.post(url, data=payload)
        response.raise_for_status()

        soup = BeautifulSoup(response.text, 'html.parser')
        formatted_dax_div = soup.find('div', {'class': 'formatted'})

        if formatted_dax_div is None:
            return "Error: No formatted DAX found on the page."

        for br in formatted_dax_div.find_all("br"):
            br.replace_with("\n")

        formatted_dax_text = formatted_dax_div.get_text(' ').replace('\xa0', ' ')

        return formatted_dax_text
    except Exception as e:
        return f"Error: {e}"

#Example usage
# format_dax(dax_expression= "SUM (Sales[SalesAmount])", measure_df=None, measure_name=None)

Format And Save Formatted DAX

import Microsoft.AnalysisServices.Tabular as tom

def save_formatted_dax_measures(workspace, dataset):

    """
    Sandeep Pawar  |   fabric.guru   | 06-13-2024
    Format and save all DAX measures in a semantic model. 
    Be sure to back up the measures before modifying the model.
    Once saved, the model cannnot be downloaded from service.

    """
    tom_server = fabric.create_tom_server(False, workspace)
    tom_database = tom_server.Databases.GetByName(dataset)
    model = tom_database.Model  
    for table in model.Tables:
        for measure in table.Measures:
            print("Formatting Measure: ", measure.Name)
            formatted = str(format_dax(dax_expression=measure.Expression, measure_df = None, measure_name = None))
            measure.Expression = formatted
    model.SaveChanges()  
    fabric.refresh_tom_cache(workspace=workspace)

save_formatted_dax_measures(workspace=ws, dataset=ds)

Bonus : Add DAX To The Measure Description

To add the measure definition to the measure description so it's visible to report authors in live mode scenarios, use following:

import Microsoft.AnalysisServices.Tabular as tom

def save_formatted_dax_measures(workspace, dataset):

    """
    Sandeep Pawar  |   fabric.guru   | 06-13-2024
    Format and save all DAX measures in a semantic model. 
    Adds DAX to measure description.
    Be sure to back up the measures before modifying the model.
    Once saved, the model cannnot be downloaded from service.

    """
    tom_server = fabric.create_tom_server(False, workspace)
    tom_database = tom_server.Databases.GetByName(dataset)
    model = tom_database.Model  
    for table in model.Tables:
        for measure in table.Measures:
            print("Formatting Measure: ", measure.Name)
            formatted = str(format_dax(dax_expression=measure.Expression, measure_df = None, measure_name = None))
            measure.Expression = formatted
            measure.Description = formatted
    model.SaveChanges()  
    fabric.refresh_tom_cache(workspace=workspace)

save_formatted_dax_measures(workspace=ws, dataset=ds)

Example

Here are before and after:

This script only formats the measures. You can easily modify it to include calculated columns and tables. You can also loop over all models to format DAX in all models in your tenant or use it in a pipeline to make sure the DAX is always formatted after publishing the model !

Note

If you use Tabular Editor, it's easy to do it using C# in TE2/3.

Did you find this article valuable?

Support Sandeep Pawar by becoming a sponsor. Any amount is appreciated!