Skip to content

Commit ebc659f

Browse files
committed
Merge branch 'm-kovalsky/tom_service_principal'
2 parents e738a7a + c04bdd0 commit ebc659f

File tree

1 file changed

+97
-16
lines changed

1 file changed

+97
-16
lines changed

src/sempy_labs/tom/_model.py

Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from sempy.fabric.exceptions import FabricHTTPException
2121
import ast
2222
from uuid import UUID
23+
from sempy.fabric._token_provider import TokenProvider
2324

2425
if TYPE_CHECKING:
2526
import Microsoft.AnalysisServices.Tabular
@@ -42,21 +43,91 @@ class TOMWrapper:
4243
_table_map = dict
4344
_column_map = dict
4445

45-
def __init__(self, dataset, workspace, readonly):
46-
47-
(workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
48-
(dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id)
49-
self._dataset_id = dataset_id
50-
self._dataset_name = dataset_name
51-
self._workspace_name = workspace_name
52-
self._workspace_id = workspace_id
46+
def __init__(self, dataset, workspace, readonly, token_provider):
47+
48+
self._is_azure_as = False
49+
prefix = "asazure"
50+
prefix_full = f"{prefix}://"
51+
read_write = ":rw"
52+
53+
# Azure AS workspace logic
54+
if workspace.startswith(prefix_full):
55+
# Set read or read/write accordingly
56+
if readonly is False and not workspace.endswith(read_write):
57+
workspace += read_write
58+
elif readonly is True and workspace.endswith(read_write):
59+
workspace = workspace[: -len(read_write)]
60+
self._workspace_name = workspace
61+
self._workspace_id
62+
self._dataset_id = dataset
63+
self._dataset_name = dataset
64+
self._is_azure_as = True
65+
if token_provider is None:
66+
raise ValueError(
67+
f"{icons.red_dot} A token provider must be provided when connecting to an Azure AS workspace."
68+
)
69+
else:
70+
(workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace)
71+
(dataset_name, dataset_id) = resolve_dataset_name_and_id(
72+
dataset, workspace_id
73+
)
74+
self._dataset_id = dataset_id
75+
self._dataset_name = dataset_name
76+
self._workspace_name = workspace_name
77+
self._workspace_id = workspace_id
5378
self._readonly = readonly
5479
self._tables_added = []
80+
self._token_provider = token_provider
5581

56-
self._tom_server = fabric.create_tom_server(
57-
readonly=readonly, workspace=workspace_id
58-
)
59-
self.model = self._tom_server.Databases[dataset_id].Model
82+
# No token provider (standard authentication)
83+
if self._token_provider is None:
84+
self._tom_server = fabric.create_tom_server(
85+
readonly=readonly, workspace=workspace_id
86+
)
87+
# Service Principal Authentication for Azure AS via token provider
88+
elif self._is_azure_as:
89+
import Microsoft.AnalysisServices.Tabular as TOM
90+
91+
# Extract region from the workspace
92+
match = re.search(rf"{prefix_full}(.*?).{prefix}", self._workspace_name)
93+
if match:
94+
region = match.group(1)
95+
if self._token_provider is None:
96+
raise ValueError(f"{icons.red_dot} A token provider must be provided when connecting to Azure Analysis Services.")
97+
token = self._token_provider(audience="asazure", region=region)
98+
connection_str = f'Provider=MSOLAP;Data Source={self._workspace_name};Password="{token}";Persist Security Info=True;Impersonation Level=Impersonate'
99+
self._tom_server = TOM.Server()
100+
self._tom_server.Connect(connection_str)
101+
# Service Principal Authentication for Power BI via token provider
102+
else:
103+
from sempy.fabric._client._utils import _build_adomd_connection_string
104+
import Microsoft.AnalysisServices.Tabular as TOM
105+
from Microsoft.AnalysisServices import AccessToken
106+
from sempy.fabric._token_provider import (
107+
create_on_access_token_expired_callback,
108+
ConstantTokenProvider,
109+
)
110+
from System import Func
111+
112+
token = token_provider(audience="pbi")
113+
self._tom_server = TOM.Server()
114+
get_access_token = create_on_access_token_expired_callback(
115+
ConstantTokenProvider(token)
116+
)
117+
self._tom_server.AccessToken = get_access_token(None)
118+
self._tom_server.OnAccessTokenExpired = Func[AccessToken, AccessToken](
119+
get_access_token
120+
)
121+
workspace_url = f"powerbi://api.powerbi.com/v1.0/myorg/{workspace}"
122+
connection_str = _build_adomd_connection_string(
123+
workspace_url, readonly=readonly
124+
)
125+
self._tom_server.Connect(connection_str)
126+
127+
if self._is_azure_as:
128+
self.model = self._tom_server.Databases.GetByName(self._dataset_name).Model
129+
else:
130+
self.model = self._tom_server.Databases[dataset_id].Model
60131

61132
self._table_map = {}
62133
self._column_map = {}
@@ -4666,7 +4737,10 @@ def close(self):
46664737
@log
46674738
@contextmanager
46684739
def connect_semantic_model(
4669-
dataset: str | UUID, readonly: bool = True, workspace: Optional[str] = None
4740+
dataset: str | UUID,
4741+
readonly: bool = True,
4742+
workspace: Optional[str | UUID] = None,
4743+
token_provider: Optional[TokenProvider] = None,
46704744
) -> Iterator[TOMWrapper]:
46714745
"""
46724746
Connects to the Tabular Object Model (TOM) within a semantic model.
@@ -4678,10 +4752,12 @@ def connect_semantic_model(
46784752
readonly: bool, default=True
46794753
Whether the connection is read-only or read/write. Setting this to False enables read/write which saves the changes made back to the server.
46804754
workspace : str | uuid.UUID, default=None
4681-
The Fabric workspace name or ID.
4755+
The Fabric workspace name or ID. Also supports Azure Analysis Services (token_provider required).
4756+
If connecting to Azure Analysis Services, enter the workspace parameter in the following format: 'asazure://<region>.asazure.windows.net/<server_name>'.
46824757
Defaults to None which resolves to the workspace of the attached lakehouse
46834758
or if no lakehouse attached, resolves to the workspace of the notebook.
4684-
4759+
token_provider : TokenProvider, default=None
4760+
The token provider for authentication, created by using the ServicePrincipalTokenProvider class. Required when connecting to Azure Analysis Services.
46854761
Returns
46864762
-------
46874763
typing.Iterator[TOMWrapper]
@@ -4691,7 +4767,12 @@ def connect_semantic_model(
46914767
# initialize .NET to make sure System and Microsoft.AnalysisServices.Tabular is defined
46924768
sempy.fabric._client._utils._init_analysis_services()
46934769

4694-
tw = TOMWrapper(dataset=dataset, workspace=workspace, readonly=readonly)
4770+
tw = TOMWrapper(
4771+
dataset=dataset,
4772+
workspace=workspace,
4773+
readonly=readonly,
4774+
token_provider=token_provider,
4775+
)
46954776
try:
46964777
yield tw
46974778
finally:

0 commit comments

Comments
 (0)