semantic-link-labs 0.10.1__py3-none-any.whl → 0.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of semantic-link-labs might be problematic. Click here for more details.
- {semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/METADATA +8 -6
- {semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/RECORD +94 -92
- sempy_labs/__init__.py +4 -0
- sempy_labs/_a_lib_info.py +1 -1
- sempy_labs/_capacities.py +2 -0
- sempy_labs/_connections.py +11 -0
- sempy_labs/_dashboards.py +9 -4
- sempy_labs/_data_pipelines.py +5 -0
- sempy_labs/_dataflows.py +284 -17
- sempy_labs/_daxformatter.py +2 -0
- sempy_labs/_delta_analyzer_history.py +4 -1
- sempy_labs/_deployment_pipelines.py +4 -0
- sempy_labs/_documentation.py +3 -0
- sempy_labs/_environments.py +10 -1
- sempy_labs/_eventhouses.py +12 -5
- sempy_labs/_eventstreams.py +11 -3
- sempy_labs/_external_data_shares.py +8 -2
- sempy_labs/_gateways.py +26 -5
- sempy_labs/_git.py +11 -0
- sempy_labs/_graphQL.py +10 -3
- sempy_labs/_helper_functions.py +62 -10
- sempy_labs/_job_scheduler.py +54 -7
- sempy_labs/_kql_databases.py +11 -2
- sempy_labs/_kql_querysets.py +11 -3
- sempy_labs/_list_functions.py +17 -2
- sempy_labs/_managed_private_endpoints.py +11 -2
- sempy_labs/_mirrored_databases.py +17 -3
- sempy_labs/_mirrored_warehouses.py +9 -3
- sempy_labs/_ml_experiments.py +11 -3
- sempy_labs/_ml_models.py +11 -3
- sempy_labs/_model_bpa_rules.py +2 -0
- sempy_labs/_mounted_data_factories.py +12 -8
- sempy_labs/_notebooks.py +3 -0
- sempy_labs/_refresh_semantic_model.py +1 -0
- sempy_labs/_semantic_models.py +6 -0
- sempy_labs/_spark.py +7 -0
- sempy_labs/_sql_endpoints.py +54 -31
- sempy_labs/_sqldatabase.py +13 -4
- sempy_labs/_tags.py +5 -1
- sempy_labs/_user_delegation_key.py +2 -0
- sempy_labs/_variable_libraries.py +3 -1
- sempy_labs/_warehouses.py +13 -3
- sempy_labs/_workloads.py +3 -0
- sempy_labs/_workspace_identity.py +3 -0
- sempy_labs/_workspaces.py +14 -1
- sempy_labs/admin/__init__.py +2 -0
- sempy_labs/admin/_activities.py +6 -5
- sempy_labs/admin/_apps.py +31 -31
- sempy_labs/admin/_artifacts.py +8 -3
- sempy_labs/admin/_basic_functions.py +5 -0
- sempy_labs/admin/_capacities.py +33 -20
- sempy_labs/admin/_datasets.py +51 -51
- sempy_labs/admin/_domains.py +17 -1
- sempy_labs/admin/_external_data_share.py +8 -2
- sempy_labs/admin/_git.py +14 -9
- sempy_labs/admin/_items.py +15 -2
- sempy_labs/admin/_reports.py +64 -65
- sempy_labs/admin/_shared.py +7 -1
- sempy_labs/admin/_tags.py +5 -0
- sempy_labs/admin/_tenant.py +5 -2
- sempy_labs/admin/_users.py +9 -3
- sempy_labs/admin/_workspaces.py +88 -0
- sempy_labs/directlake/_dl_helper.py +2 -0
- sempy_labs/directlake/_generate_shared_expression.py +2 -0
- sempy_labs/directlake/_get_directlake_lakehouse.py +2 -4
- sempy_labs/directlake/_get_shared_expression.py +2 -0
- sempy_labs/directlake/_guardrails.py +2 -0
- sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +2 -0
- sempy_labs/directlake/_warm_cache.py +1 -0
- sempy_labs/graph/_groups.py +22 -7
- sempy_labs/graph/_teams.py +7 -2
- sempy_labs/graph/_users.py +1 -0
- sempy_labs/lakehouse/_blobs.py +1 -0
- sempy_labs/lakehouse/_get_lakehouse_tables.py +88 -27
- sempy_labs/lakehouse/_helper.py +2 -0
- sempy_labs/lakehouse/_lakehouse.py +38 -5
- sempy_labs/lakehouse/_livy_sessions.py +2 -1
- sempy_labs/lakehouse/_shortcuts.py +7 -1
- sempy_labs/migration/_direct_lake_to_import.py +2 -0
- sempy_labs/mirrored_azure_databricks_catalog/_discover.py +4 -0
- sempy_labs/mirrored_azure_databricks_catalog/_refresh_catalog_metadata.py +2 -0
- sempy_labs/report/_download_report.py +2 -1
- sempy_labs/report/_generate_report.py +2 -0
- sempy_labs/report/_paginated.py +2 -0
- sempy_labs/report/_report_bpa.py +110 -122
- sempy_labs/report/_report_bpa_rules.py +2 -0
- sempy_labs/report/_report_functions.py +7 -0
- sempy_labs/report/_reportwrapper.py +64 -31
- sempy_labs/theme/__init__.py +12 -0
- sempy_labs/theme/_org_themes.py +117 -0
- sempy_labs/tom/_model.py +494 -16
- {semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/WHEEL +0 -0
- {semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/licenses/LICENSE +0 -0
- {semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/top_level.txt +0 -0
sempy_labs/tom/_model.py
CHANGED
|
@@ -2,8 +2,10 @@ import sempy
|
|
|
2
2
|
import sempy.fabric as fabric
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import re
|
|
5
|
+
import os
|
|
5
6
|
import json
|
|
6
7
|
from datetime import datetime
|
|
8
|
+
from decimal import Decimal
|
|
7
9
|
from sempy_labs._helper_functions import (
|
|
8
10
|
format_dax_object_name,
|
|
9
11
|
generate_guid,
|
|
@@ -14,17 +16,19 @@ from sempy_labs._helper_functions import (
|
|
|
14
16
|
resolve_workspace_id,
|
|
15
17
|
resolve_item_id,
|
|
16
18
|
resolve_lakehouse_id,
|
|
19
|
+
_validate_weight,
|
|
17
20
|
)
|
|
18
21
|
from sempy_labs._list_functions import list_relationships
|
|
19
22
|
from sempy_labs._refresh_semantic_model import refresh_semantic_model
|
|
20
23
|
from sempy_labs.directlake._dl_helper import check_fallback_reason
|
|
21
24
|
from contextlib import contextmanager
|
|
22
|
-
from typing import List, Iterator, Optional, Union, TYPE_CHECKING
|
|
25
|
+
from typing import List, Iterator, Optional, Union, TYPE_CHECKING, Literal
|
|
23
26
|
from sempy._utils._log import log
|
|
24
27
|
import sempy_labs._icons as icons
|
|
25
28
|
import ast
|
|
26
29
|
from uuid import UUID
|
|
27
30
|
import sempy_labs._authentication as auth
|
|
31
|
+
from sempy_labs.lakehouse._lakehouse import lakehouse_attached
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
if TYPE_CHECKING:
|
|
@@ -784,7 +788,11 @@ class TOMWrapper:
|
|
|
784
788
|
self.model.Roles[role_name].TablePermissions.Add(tp)
|
|
785
789
|
|
|
786
790
|
def set_ols(
|
|
787
|
-
self,
|
|
791
|
+
self,
|
|
792
|
+
role_name: str,
|
|
793
|
+
table_name: str,
|
|
794
|
+
column_name: Optional[str] = None,
|
|
795
|
+
permission: Literal["Default", "None", "Read"] = "Default",
|
|
788
796
|
):
|
|
789
797
|
"""
|
|
790
798
|
Sets the object level security permissions for a column within a role.
|
|
@@ -795,9 +803,9 @@ class TOMWrapper:
|
|
|
795
803
|
Name of the role.
|
|
796
804
|
table_name : str
|
|
797
805
|
Name of the table.
|
|
798
|
-
column_name : str
|
|
799
|
-
Name of the column.
|
|
800
|
-
permission :
|
|
806
|
+
column_name : str, default=None
|
|
807
|
+
Name of the column. Defaults to None which sets object level security for the entire table.
|
|
808
|
+
permission : Literal["Default", "None", "Read"], default="Default"
|
|
801
809
|
The object level security permission for the column.
|
|
802
810
|
`Permission valid values <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.metadatapermission?view=analysisservices-dotnet>`_
|
|
803
811
|
"""
|
|
@@ -817,19 +825,29 @@ class TOMWrapper:
|
|
|
817
825
|
tp.Table = self.model.Tables[table_name]
|
|
818
826
|
r.TablePermissions.Add(tp)
|
|
819
827
|
columns = [c.Name for c in r.TablePermissions[table_name].ColumnPermissions]
|
|
820
|
-
|
|
821
|
-
if
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
828
|
+
|
|
829
|
+
# Set column level security if column is specified
|
|
830
|
+
if column_name:
|
|
831
|
+
# Add column permission if it does not exist
|
|
832
|
+
if column_name not in columns:
|
|
833
|
+
cp = TOM.ColumnPermission()
|
|
834
|
+
cp.Column = self.model.Tables[table_name].Columns[column_name]
|
|
835
|
+
cp.MetadataPermission = System.Enum.Parse(
|
|
836
|
+
TOM.MetadataPermission, permission
|
|
837
|
+
)
|
|
838
|
+
r.TablePermissions[table_name].ColumnPermissions.Add(cp)
|
|
839
|
+
# Set column permission if it already exists
|
|
840
|
+
else:
|
|
841
|
+
r.TablePermissions[table_name].ColumnPermissions[
|
|
842
|
+
column_name
|
|
843
|
+
].MetadataPermission = System.Enum.Parse(
|
|
844
|
+
TOM.MetadataPermission, permission
|
|
845
|
+
)
|
|
846
|
+
# Set table level security if column is not specified
|
|
847
|
+
else:
|
|
848
|
+
r.TablePermissions[table_name].MetadataPermission = System.Enum.Parse(
|
|
825
849
|
TOM.MetadataPermission, permission
|
|
826
850
|
)
|
|
827
|
-
r.TablePermissions[table_name].ColumnPermissions.Add(cp)
|
|
828
|
-
# Set column permission if it already exists
|
|
829
|
-
else:
|
|
830
|
-
r.TablePermissions[table_name].ColumnPermissions[
|
|
831
|
-
column_name
|
|
832
|
-
].MetadataPermission = System.Enum.Parse(TOM.MetadataPermission, permission)
|
|
833
851
|
|
|
834
852
|
def add_hierarchy(
|
|
835
853
|
self,
|
|
@@ -4822,6 +4840,47 @@ class TOMWrapper:
|
|
|
4822
4840
|
|
|
4823
4841
|
return bim
|
|
4824
4842
|
|
|
4843
|
+
def clear_linguistic_schema(self, culture: str):
|
|
4844
|
+
"""
|
|
4845
|
+
Clears the linguistic schema for a given culture.
|
|
4846
|
+
|
|
4847
|
+
Parameters
|
|
4848
|
+
----------
|
|
4849
|
+
culture : str
|
|
4850
|
+
The culture name.
|
|
4851
|
+
"""
|
|
4852
|
+
|
|
4853
|
+
empty_schema = f'{{"Version":"1.0.0","Language":"{culture}"}}'
|
|
4854
|
+
|
|
4855
|
+
self.model.Cultures[culture].LinguisticMetadata.Content = json.dumps(
|
|
4856
|
+
empty_schema, indent=4
|
|
4857
|
+
)
|
|
4858
|
+
|
|
4859
|
+
def get_linguistic_schema(self, culture: str) -> dict:
|
|
4860
|
+
"""
|
|
4861
|
+
Obtains the linguistic schema for a given culture.
|
|
4862
|
+
|
|
4863
|
+
Parameters
|
|
4864
|
+
----------
|
|
4865
|
+
culture : str
|
|
4866
|
+
The culture name.
|
|
4867
|
+
|
|
4868
|
+
Returns
|
|
4869
|
+
-------
|
|
4870
|
+
dict
|
|
4871
|
+
The .bim file.
|
|
4872
|
+
"""
|
|
4873
|
+
|
|
4874
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
4875
|
+
|
|
4876
|
+
bim = (
|
|
4877
|
+
json.loads(TOM.JsonScripter.ScriptCreate(self.model.Database))
|
|
4878
|
+
.get("create")
|
|
4879
|
+
.get("database")
|
|
4880
|
+
)
|
|
4881
|
+
|
|
4882
|
+
return bim
|
|
4883
|
+
|
|
4825
4884
|
def _reduce_model(self, perspective_name: str):
|
|
4826
4885
|
"""
|
|
4827
4886
|
Reduces a model's objects based on a perspective. Adds the dependent objects within a perspective to that perspective.
|
|
@@ -5261,6 +5320,425 @@ class TOMWrapper:
|
|
|
5261
5320
|
f"{icons.red_dot} The '{str(object.ObjectType)}' object type is not supported for DAX formatting."
|
|
5262
5321
|
)
|
|
5263
5322
|
|
|
5323
|
+
def get_linguistic_schema(self, culture: str) -> dict:
|
|
5324
|
+
"""
|
|
5325
|
+
Obtains the linguistic schema for a given culture.
|
|
5326
|
+
Parameters
|
|
5327
|
+
----------
|
|
5328
|
+
culture : str
|
|
5329
|
+
The culture name.
|
|
5330
|
+
Returns
|
|
5331
|
+
-------
|
|
5332
|
+
dict
|
|
5333
|
+
The linguistic schema for the given culture.
|
|
5334
|
+
"""
|
|
5335
|
+
|
|
5336
|
+
c = self.model.Cultures[culture]
|
|
5337
|
+
if c.LinguisticMetadata is not None:
|
|
5338
|
+
return json.loads(c.LinguisticMetadata.Content)
|
|
5339
|
+
else:
|
|
5340
|
+
print(
|
|
5341
|
+
f"{icons.info} The '{culture}' culture does not have a linguistic schema."
|
|
5342
|
+
)
|
|
5343
|
+
return None
|
|
5344
|
+
|
|
5345
|
+
def _add_linguistic_schema(self, culture: str):
|
|
5346
|
+
|
|
5347
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
5348
|
+
|
|
5349
|
+
# TODO: if LinguisticMetadata is None
|
|
5350
|
+
# TODO: check if lower() is good enough
|
|
5351
|
+
# TODO: 'in' vs 'has' in relationships
|
|
5352
|
+
# TODO: 'SemanticSlots' in relationships
|
|
5353
|
+
|
|
5354
|
+
c = self.model.Cultures[culture]
|
|
5355
|
+
if c.LinguisticMetadata is not None:
|
|
5356
|
+
lm = json.loads(c.LinguisticMetadata.Content)
|
|
5357
|
+
|
|
5358
|
+
def add_entity(entity, conecptual_entity, conceptual_property):
|
|
5359
|
+
lm["Entities"][entity] = {
|
|
5360
|
+
"Definition": {
|
|
5361
|
+
"Binding": {
|
|
5362
|
+
"ConceptualEntity": conecptual_entity,
|
|
5363
|
+
"ConceptualProperty": conceptual_property,
|
|
5364
|
+
}
|
|
5365
|
+
},
|
|
5366
|
+
"State": "Generated",
|
|
5367
|
+
"Terms": [],
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5370
|
+
def add_relationship(rel_key, table_name, t_name, o_name):
|
|
5371
|
+
lm["Relationships"][rel_key] = {
|
|
5372
|
+
"Binding": {"ConceptualEntity": table_name},
|
|
5373
|
+
"State": "Generated",
|
|
5374
|
+
"Roles": {
|
|
5375
|
+
t_name: {"Target": {"Entity": t_name}},
|
|
5376
|
+
f"{t_name}.{o_name}": {
|
|
5377
|
+
"Target": {"Entity": f"{t_name}.{o_name}"}
|
|
5378
|
+
},
|
|
5379
|
+
},
|
|
5380
|
+
"Phrasings": [
|
|
5381
|
+
{
|
|
5382
|
+
"Attribute": {
|
|
5383
|
+
"Subject": {"Role": t_name},
|
|
5384
|
+
"Object": {"Role": f"{t_name}.{o_name}"},
|
|
5385
|
+
},
|
|
5386
|
+
"State": "Generated",
|
|
5387
|
+
"Weight": 0.99,
|
|
5388
|
+
"ID": f"{t_name}_have_{o_name}",
|
|
5389
|
+
}
|
|
5390
|
+
],
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
if "Entities" not in lm:
|
|
5394
|
+
lm["Entities"] = {}
|
|
5395
|
+
for t in self.model.Tables:
|
|
5396
|
+
t_lower = t.Name.lower()
|
|
5397
|
+
lm["Entities"][t_lower] = {
|
|
5398
|
+
"Definition": {"Binding": {"ConceptualEntity": t.Name}},
|
|
5399
|
+
"State": "Generated",
|
|
5400
|
+
"Terms": [],
|
|
5401
|
+
}
|
|
5402
|
+
for c in t.Columns:
|
|
5403
|
+
if c.Type != TOM.ColumnType.RowNumber:
|
|
5404
|
+
c_lower = f"{t_lower}.{c.Name.lower()}"
|
|
5405
|
+
add_entity(c_lower, t.Name, c.Name)
|
|
5406
|
+
for m in t.Measures:
|
|
5407
|
+
m_lower = f"{t_lower}.{m.Name.lower()}"
|
|
5408
|
+
add_entity(m_lower, t.Name, m.Name)
|
|
5409
|
+
for h in t.Hierarchies:
|
|
5410
|
+
h_lower = f"{t_lower}.{h.Name.lower()}"
|
|
5411
|
+
add_entity(h_lower, t.Name, h.Name)
|
|
5412
|
+
# if "Relationships" not in lm:
|
|
5413
|
+
# lm["Relationships"] = {}
|
|
5414
|
+
# for c in self.all_columns():
|
|
5415
|
+
# table_name = c.Parent.Name
|
|
5416
|
+
# t_name = table_name.lower()
|
|
5417
|
+
# object_name = c.Name
|
|
5418
|
+
# o_name = object_name.lower()
|
|
5419
|
+
# rel_key = f"{t_name}_has_{o_name}"
|
|
5420
|
+
# add_relationship(rel_key, table_name, t_name, o_name)
|
|
5421
|
+
# for m in self.all_measures():
|
|
5422
|
+
# table_name = c.Parent.Name
|
|
5423
|
+
# t_name = table_name.lower()
|
|
5424
|
+
# object_name = m.Name
|
|
5425
|
+
# o_name = object_name.lower()
|
|
5426
|
+
# rel_key = f"{t_name}_has_{o_name}"
|
|
5427
|
+
# add_relationship(rel_key, table_name, t_name, o_name)
|
|
5428
|
+
|
|
5429
|
+
self.model.Cultures[culture].LinguisticMetadata.Content = json.dumps(lm)
|
|
5430
|
+
|
|
5431
|
+
@staticmethod
|
|
5432
|
+
def _get_synonym_info(
|
|
5433
|
+
lm: dict,
|
|
5434
|
+
object: Union["TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy"],
|
|
5435
|
+
synonym_name: str,
|
|
5436
|
+
):
|
|
5437
|
+
|
|
5438
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
5439
|
+
|
|
5440
|
+
object_type = object.ObjectType
|
|
5441
|
+
obj = None
|
|
5442
|
+
syn_exists = False
|
|
5443
|
+
|
|
5444
|
+
for key, v in lm.get("Entities", []).items():
|
|
5445
|
+
binding = v.get("Definition", {}).get("Binding", {})
|
|
5446
|
+
t_name = binding.get("ConceptualEntity")
|
|
5447
|
+
o_name = binding.get("ConceptualProperty")
|
|
5448
|
+
|
|
5449
|
+
if (
|
|
5450
|
+
object_type == TOM.ObjectType.Table
|
|
5451
|
+
and t_name == object.Name
|
|
5452
|
+
and o_name is None
|
|
5453
|
+
) or (
|
|
5454
|
+
object_type
|
|
5455
|
+
in [
|
|
5456
|
+
TOM.ObjectType.Column,
|
|
5457
|
+
TOM.ObjectType.Measure,
|
|
5458
|
+
TOM.ObjectType.Hierarchy,
|
|
5459
|
+
]
|
|
5460
|
+
and t_name == object.Parent.Name
|
|
5461
|
+
and o_name == object.Name
|
|
5462
|
+
):
|
|
5463
|
+
obj = key
|
|
5464
|
+
terms = v.get("Terms", [])
|
|
5465
|
+
syn_exists = any(synonym_name in term for term in terms)
|
|
5466
|
+
# optionally break early if match is found
|
|
5467
|
+
break
|
|
5468
|
+
|
|
5469
|
+
return obj, syn_exists
|
|
5470
|
+
|
|
5471
|
+
def set_synonym(
|
|
5472
|
+
self,
|
|
5473
|
+
culture: str,
|
|
5474
|
+
object: Union["TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy"],
|
|
5475
|
+
synonym_name: str,
|
|
5476
|
+
weight: Optional[Decimal] = None,
|
|
5477
|
+
):
|
|
5478
|
+
"""
|
|
5479
|
+
Sets a synonym for a table/column/measure/hierarchy in the linguistic schema of the semantic model. This function is currently in preview.
|
|
5480
|
+
|
|
5481
|
+
Parameters
|
|
5482
|
+
----------
|
|
5483
|
+
culture : str
|
|
5484
|
+
The culture name for which the synonym is being set. Example: 'en-US'.
|
|
5485
|
+
object : TOM Object
|
|
5486
|
+
The TOM object for which the synonym is being set. This can be a table, column, measure, or hierarchy.
|
|
5487
|
+
synonym_name : str
|
|
5488
|
+
The name of the synonym to be set.
|
|
5489
|
+
weight : Decimal, default=None
|
|
5490
|
+
The weight of the synonym. If None, the default weight is used. The weight must be a Decimal value between 0 and 1.
|
|
5491
|
+
"""
|
|
5492
|
+
|
|
5493
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
5494
|
+
|
|
5495
|
+
object_type = object.ObjectType
|
|
5496
|
+
|
|
5497
|
+
if object_type not in [
|
|
5498
|
+
TOM.ObjectType.Table,
|
|
5499
|
+
TOM.ObjectType.Column,
|
|
5500
|
+
TOM.ObjectType.Measure,
|
|
5501
|
+
TOM.ObjectType.Hierarchy,
|
|
5502
|
+
]:
|
|
5503
|
+
raise ValueError(
|
|
5504
|
+
f"{icons.red_dot} This function only supports adding synonyms for tables/columns/measures/hierarchies."
|
|
5505
|
+
)
|
|
5506
|
+
|
|
5507
|
+
# Add base linguistic schema in case it does not yet exist
|
|
5508
|
+
self._add_linguistic_schema(culture=culture)
|
|
5509
|
+
|
|
5510
|
+
# Extract linguistic metadata content
|
|
5511
|
+
lm = json.loads(self.model.Cultures[culture].LinguisticMetadata.Content)
|
|
5512
|
+
|
|
5513
|
+
# Generate synonym dictionary
|
|
5514
|
+
_validate_weight(weight)
|
|
5515
|
+
now = datetime.now().isoformat(timespec="milliseconds") + "Z"
|
|
5516
|
+
syn_dict = {"Type": "Noun", "State": "Authored", "LastModified": now}
|
|
5517
|
+
if weight is not None:
|
|
5518
|
+
syn_dict["Weight"] = weight
|
|
5519
|
+
|
|
5520
|
+
updated = False
|
|
5521
|
+
|
|
5522
|
+
(obj, syn_exists) = self._get_synonym_info(
|
|
5523
|
+
lm=lm, object=object, synonym_name=synonym_name
|
|
5524
|
+
)
|
|
5525
|
+
|
|
5526
|
+
entities = lm.get("Entities", {})
|
|
5527
|
+
|
|
5528
|
+
def get_unique_entity_key(object, object_type, entities):
|
|
5529
|
+
|
|
5530
|
+
if object_type == TOM.ObjectType.Table:
|
|
5531
|
+
base_obj = object.Name.lower().replace(" ", "_")
|
|
5532
|
+
else:
|
|
5533
|
+
base_obj = f"{object.Parent.Name}.{object.Name}".lower().replace(
|
|
5534
|
+
" ", "_"
|
|
5535
|
+
)
|
|
5536
|
+
|
|
5537
|
+
obj = base_obj
|
|
5538
|
+
counter = 1
|
|
5539
|
+
existing_keys = set(entities.keys())
|
|
5540
|
+
|
|
5541
|
+
# Make sure the object name is unique
|
|
5542
|
+
while obj in existing_keys:
|
|
5543
|
+
obj = f"{base_obj}_{counter}"
|
|
5544
|
+
counter += 1
|
|
5545
|
+
|
|
5546
|
+
return obj
|
|
5547
|
+
|
|
5548
|
+
# Update linguistic metadata content
|
|
5549
|
+
if obj is None:
|
|
5550
|
+
obj = get_unique_entity_key(object, object_type, entities)
|
|
5551
|
+
lm["Entities"][obj] = {
|
|
5552
|
+
"Definition": {"Binding": {}},
|
|
5553
|
+
"State": "Authored",
|
|
5554
|
+
"Terms": [
|
|
5555
|
+
{synonym_name: syn_dict},
|
|
5556
|
+
],
|
|
5557
|
+
}
|
|
5558
|
+
if object_type == TOM.ObjectType.Table:
|
|
5559
|
+
lm["Entities"][obj]["Definition"]["Binding"][
|
|
5560
|
+
"ConceptualEntity"
|
|
5561
|
+
] = object.Name
|
|
5562
|
+
else:
|
|
5563
|
+
lm["Entities"][obj]["Definition"]["Binding"][
|
|
5564
|
+
"ConceptualEntity"
|
|
5565
|
+
] = object.Parent.Name
|
|
5566
|
+
lm["Entities"][obj]["Definition"]["Binding"][
|
|
5567
|
+
"ConceptualProperty"
|
|
5568
|
+
] = object.Name
|
|
5569
|
+
updated = True
|
|
5570
|
+
elif not syn_exists:
|
|
5571
|
+
lm["Entities"][obj]["Terms"].append({synonym_name: syn_dict})
|
|
5572
|
+
updated = True
|
|
5573
|
+
else:
|
|
5574
|
+
for term in lm["Entities"][obj]["Terms"]:
|
|
5575
|
+
if term == synonym_name:
|
|
5576
|
+
lm["Entities"][obj]["Terms"][term] = syn_dict
|
|
5577
|
+
updated = True
|
|
5578
|
+
|
|
5579
|
+
if "State" in lm["Entities"][obj]:
|
|
5580
|
+
del lm["Entities"][obj]["State"]
|
|
5581
|
+
|
|
5582
|
+
if updated:
|
|
5583
|
+
self.model.Cultures[culture].LinguisticMetadata.Content = json.dumps(
|
|
5584
|
+
lm, indent=4
|
|
5585
|
+
)
|
|
5586
|
+
if object_type == TOM.ObjectType.Table:
|
|
5587
|
+
print(
|
|
5588
|
+
f"{icons.green_dot} The '{synonym_name}' synonym was set for the '{object.Name}' table."
|
|
5589
|
+
)
|
|
5590
|
+
else:
|
|
5591
|
+
print(
|
|
5592
|
+
f"{icons.green_dot} The '{synonym_name}' synonym was set for the '{object.Parent.Name}'[{object.Name}] column."
|
|
5593
|
+
)
|
|
5594
|
+
|
|
5595
|
+
def delete_synonym(
|
|
5596
|
+
self,
|
|
5597
|
+
culture: str,
|
|
5598
|
+
object: Union["TOM.Table", "TOM.Column", "TOM.Measure", "TOM.Hierarchy"],
|
|
5599
|
+
synonym_name: str,
|
|
5600
|
+
):
|
|
5601
|
+
"""
|
|
5602
|
+
Deletes a synonym for a table/column/measure/hierarchy in the linguistic schema of the semantic model. This function is currently in preview.
|
|
5603
|
+
|
|
5604
|
+
Parameters
|
|
5605
|
+
----------
|
|
5606
|
+
culture : str
|
|
5607
|
+
The culture name for which the synonym is being deleted. Example: 'en-US'.
|
|
5608
|
+
object : TOM Object
|
|
5609
|
+
The TOM object for which the synonym is being deleted. This can be a table, column, measure, or hierarchy.
|
|
5610
|
+
synonym_name : str
|
|
5611
|
+
The name of the synonym to be deleted.
|
|
5612
|
+
"""
|
|
5613
|
+
|
|
5614
|
+
import Microsoft.AnalysisServices.Tabular as TOM
|
|
5615
|
+
|
|
5616
|
+
if not any(c.Name == culture for c in self.model.Cultures):
|
|
5617
|
+
raise ValueError(
|
|
5618
|
+
f"{icons.red_dot} The '{culture}' culture does not exist within the semantic model."
|
|
5619
|
+
)
|
|
5620
|
+
|
|
5621
|
+
if object.ObjectType not in [
|
|
5622
|
+
TOM.ObjectType.Table,
|
|
5623
|
+
TOM.ObjectType.Column,
|
|
5624
|
+
TOM.ObjectType.Measure,
|
|
5625
|
+
TOM.ObjectType.Hierarchy,
|
|
5626
|
+
]:
|
|
5627
|
+
raise ValueError(
|
|
5628
|
+
f"{icons.red_dot} This function only supports tables/columns/measures/hierarchies."
|
|
5629
|
+
)
|
|
5630
|
+
|
|
5631
|
+
lm = json.loads(self.model.Cultures[culture].LinguisticMetadata.Content)
|
|
5632
|
+
|
|
5633
|
+
if "Entities" not in lm:
|
|
5634
|
+
print(
|
|
5635
|
+
f"{icons.warning} There is no linguistic schema for the '{culture}' culture."
|
|
5636
|
+
)
|
|
5637
|
+
return
|
|
5638
|
+
|
|
5639
|
+
(obj, syn_exists) = self._get_synonym_info(
|
|
5640
|
+
lm=lm, object=object, synonym_name=synonym_name
|
|
5641
|
+
)
|
|
5642
|
+
|
|
5643
|
+
# Mark the synonym as deleted if it exists
|
|
5644
|
+
if obj is not None and syn_exists:
|
|
5645
|
+
data = lm["Entities"][obj]["Terms"]
|
|
5646
|
+
next(
|
|
5647
|
+
(
|
|
5648
|
+
item[synonym_name].update({"State": "Deleted"})
|
|
5649
|
+
for item in data
|
|
5650
|
+
if synonym_name in item
|
|
5651
|
+
),
|
|
5652
|
+
None,
|
|
5653
|
+
)
|
|
5654
|
+
|
|
5655
|
+
self.model.Cultures[culture].LinguisticMetadata.Content = json.dumps(
|
|
5656
|
+
lm, indent=4
|
|
5657
|
+
)
|
|
5658
|
+
print(
|
|
5659
|
+
f"{icons.green_dot} The '{synonym_name}' synonym was marked as status 'Deleted' for the '{object.Name}' object."
|
|
5660
|
+
)
|
|
5661
|
+
else:
|
|
5662
|
+
print(
|
|
5663
|
+
f"{icons.info} The '{synonym_name}' synonym does not exist for the '{object.Name}' object."
|
|
5664
|
+
)
|
|
5665
|
+
|
|
5666
|
+
def _lock_linguistic_schema(self, culture: str):
|
|
5667
|
+
|
|
5668
|
+
c = self.model.Cultures[culture]
|
|
5669
|
+
if c.LinguisticMetadata is not None:
|
|
5670
|
+
lm = json.loads(c.LinguisticMetadata.Content)
|
|
5671
|
+
if "DynamicImprovement" not in lm:
|
|
5672
|
+
lm["DynamicImprovement"] = {}
|
|
5673
|
+
lm["DynamicImprovement"]["Schema"] = None
|
|
5674
|
+
|
|
5675
|
+
c.LinguisticMetadata.Content = json.dumps(lm, indent=4)
|
|
5676
|
+
|
|
5677
|
+
def _unlock_linguistic_schema(self, culture: str):
|
|
5678
|
+
|
|
5679
|
+
c = self.model.Cultures[culture]
|
|
5680
|
+
if c.LinguisticMetadata is not None:
|
|
5681
|
+
lm = json.loads(c.LinguisticMetadata.Content)
|
|
5682
|
+
if "DynamicImprovement" in lm:
|
|
5683
|
+
del lm["DynamicImprovement"]["Schema"]
|
|
5684
|
+
|
|
5685
|
+
c.LinguisticMetadata.Content = json.dumps(lm, indent=4)
|
|
5686
|
+
|
|
5687
|
+
def _export_linguistic_schema(self, culture: str, file_path: str):
|
|
5688
|
+
|
|
5689
|
+
if not lakehouse_attached():
|
|
5690
|
+
raise ValueError(
|
|
5691
|
+
f"{icons.red_dot} A lakehouse must be attached to the notebook in order to export a linguistic schema."
|
|
5692
|
+
)
|
|
5693
|
+
|
|
5694
|
+
if not any(c.Name == culture for c in self.model.Cultures):
|
|
5695
|
+
raise ValueError(
|
|
5696
|
+
f"{icons.red_dot} The '{culture}' culture does not exist within the semantic model."
|
|
5697
|
+
)
|
|
5698
|
+
|
|
5699
|
+
folderPath = "/lakehouse/default/Files"
|
|
5700
|
+
fileExt = ".json"
|
|
5701
|
+
if not file_path.endswith(fileExt):
|
|
5702
|
+
file_path = f"{file_path}{fileExt}"
|
|
5703
|
+
|
|
5704
|
+
for c in self.model.Cultures:
|
|
5705
|
+
if c.Name == culture:
|
|
5706
|
+
lm = json.loads(c.LinguisticMetadata.Content)
|
|
5707
|
+
filePath = os.path.join(folderPath, file_path)
|
|
5708
|
+
with open(filePath, "w") as json_file:
|
|
5709
|
+
json.dump(lm, json_file, indent=4)
|
|
5710
|
+
|
|
5711
|
+
print(
|
|
5712
|
+
f"{icons.green_dot} The linguistic schema for the '{culture}' culture was saved as the '{file_path}' file within the lakehouse attached to this notebook."
|
|
5713
|
+
)
|
|
5714
|
+
|
|
5715
|
+
def _import_linguistic_schema(self, file_path: str):
|
|
5716
|
+
|
|
5717
|
+
if not file_path.endswith(".json"):
|
|
5718
|
+
raise ValueError(f"{icons.red_dot} The 'file_path' must be a .json file.")
|
|
5719
|
+
|
|
5720
|
+
with open(file_path, "r") as json_file:
|
|
5721
|
+
schema_file = json.load(json_file)
|
|
5722
|
+
|
|
5723
|
+
# Validate structure
|
|
5724
|
+
required_keys = ["Version", "Language", "Entities", "Relationships"]
|
|
5725
|
+
if not all(key in schema_file for key in required_keys):
|
|
5726
|
+
raise ValueError(
|
|
5727
|
+
f"{icons.red_dot} The 'schema_file' is not in the proper format."
|
|
5728
|
+
)
|
|
5729
|
+
|
|
5730
|
+
culture_name = schema_file["Language"]
|
|
5731
|
+
|
|
5732
|
+
# Validate culture
|
|
5733
|
+
if not any(c.Name == culture_name for c in self.model.Cultures):
|
|
5734
|
+
raise ValueError(
|
|
5735
|
+
f"{icons.red_dot} The culture of the schema_file is not a valid culture within the semantic model."
|
|
5736
|
+
)
|
|
5737
|
+
|
|
5738
|
+
self.model.Cultures[culture_name].LinguisticMetadata.Content = json.dumps(
|
|
5739
|
+
schema_file, indent=4
|
|
5740
|
+
)
|
|
5741
|
+
|
|
5264
5742
|
def close(self):
|
|
5265
5743
|
|
|
5266
5744
|
# DAX Formatting
|
|
File without changes
|
{semantic_link_labs-0.10.1.dist-info → semantic_link_labs-0.11.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|