semantic-link-labs 0.8.0__py3-none-any.whl → 0.8.2__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.

Files changed (47) hide show
  1. {semantic_link_labs-0.8.0.dist-info → semantic_link_labs-0.8.2.dist-info}/METADATA +39 -7
  2. {semantic_link_labs-0.8.0.dist-info → semantic_link_labs-0.8.2.dist-info}/RECORD +47 -37
  3. sempy_labs/__init__.py +70 -51
  4. sempy_labs/_ai.py +0 -2
  5. sempy_labs/_capacity_migration.py +1 -2
  6. sempy_labs/_data_pipelines.py +118 -0
  7. sempy_labs/_documentation.py +144 -0
  8. sempy_labs/_eventhouses.py +118 -0
  9. sempy_labs/_eventstreams.py +118 -0
  10. sempy_labs/_generate_semantic_model.py +3 -3
  11. sempy_labs/_git.py +3 -3
  12. sempy_labs/_helper_functions.py +117 -26
  13. sempy_labs/_icons.py +21 -0
  14. sempy_labs/_kql_databases.py +134 -0
  15. sempy_labs/_kql_querysets.py +124 -0
  16. sempy_labs/_list_functions.py +12 -425
  17. sempy_labs/_mirrored_warehouses.py +50 -0
  18. sempy_labs/_ml_experiments.py +122 -0
  19. sempy_labs/_ml_models.py +120 -0
  20. sempy_labs/_model_auto_build.py +0 -4
  21. sempy_labs/_model_bpa.py +11 -11
  22. sempy_labs/_model_bpa_bulk.py +8 -7
  23. sempy_labs/_model_dependencies.py +26 -18
  24. sempy_labs/_notebooks.py +5 -16
  25. sempy_labs/_query_scale_out.py +2 -2
  26. sempy_labs/_refresh_semantic_model.py +7 -19
  27. sempy_labs/_spark.py +10 -10
  28. sempy_labs/_vertipaq.py +16 -18
  29. sempy_labs/_warehouses.py +132 -0
  30. sempy_labs/_workspaces.py +0 -3
  31. sempy_labs/admin/_basic_functions.py +92 -10
  32. sempy_labs/admin/_domains.py +1 -1
  33. sempy_labs/directlake/_directlake_schema_sync.py +1 -1
  34. sempy_labs/directlake/_dl_helper.py +32 -16
  35. sempy_labs/directlake/_guardrails.py +7 -7
  36. sempy_labs/directlake/_update_directlake_partition_entity.py +1 -1
  37. sempy_labs/directlake/_warm_cache.py +1 -1
  38. sempy_labs/lakehouse/_get_lakehouse_tables.py +3 -3
  39. sempy_labs/lakehouse/_lakehouse.py +3 -2
  40. sempy_labs/migration/_migrate_calctables_to_lakehouse.py +5 -0
  41. sempy_labs/report/_generate_report.py +1 -1
  42. sempy_labs/report/_report_bpa.py +13 -3
  43. sempy_labs/report/_reportwrapper.py +14 -16
  44. sempy_labs/tom/_model.py +261 -24
  45. {semantic_link_labs-0.8.0.dist-info → semantic_link_labs-0.8.2.dist-info}/LICENSE +0 -0
  46. {semantic_link_labs-0.8.0.dist-info → semantic_link_labs-0.8.2.dist-info}/WHEEL +0 -0
  47. {semantic_link_labs-0.8.0.dist-info → semantic_link_labs-0.8.2.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ from sempy_labs._helper_functions import (
7
7
  _extract_json,
8
8
  _add_part,
9
9
  lro,
10
+ make_clickable,
10
11
  )
11
12
  from typing import Optional, List
12
13
  import pandas as pd
@@ -134,21 +135,17 @@ class ReportWrapper:
134
135
 
135
136
  def get_theme(self, theme_type: str = "baseTheme") -> dict:
136
137
  """
137
- Obtains
138
+ Obtains the theme file of the report.
138
139
 
139
140
  Parameters
140
141
  ----------
141
- dataset : str
142
- Name of the semantic model.
143
- workspace : str, default=None
144
- The Fabric workspace name.
145
- Defaults to None which resolves to the workspace of the attached lakehouse
146
- or if no lakehouse attached, resolves to the workspace of the notebook.
142
+ theme_type : str, default="baseTheme"
143
+ The theme type. Options: "baseTheme", "customTheme".
147
144
 
148
145
  Returns
149
146
  -------
150
- pandas.DataFrame
151
- A pandas dataframe showing a list of all columns in the semantic model which are not used in any related Power BI reports (including dependencies).
147
+ dict
148
+ The theme.json file
152
149
  """
153
150
 
154
151
  theme_types = ["baseTheme", "customTheme"]
@@ -362,8 +359,8 @@ class ReportWrapper:
362
359
  ignore_index=True,
363
360
  )
364
361
 
365
- df["Page URL"] = (
366
- f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{df['Page Name']}"
362
+ df["Page URL"] = df["Page Name"].apply(
363
+ lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{page_name}"
367
364
  )
368
365
 
369
366
  bool_cols = ["Hidden", "Locked", "Used"]
@@ -373,6 +370,7 @@ class ReportWrapper:
373
370
  df = self._add_extended(dataframe=df)
374
371
 
375
372
  return df
373
+ # return df.style.format({"Page URL": make_clickable})
376
374
 
377
375
  def list_visual_filters(self, extended: bool = False) -> pd.DataFrame:
378
376
  """
@@ -639,11 +637,12 @@ class ReportWrapper:
639
637
  bool_cols = ["Hidden", "Active", "Drillthrough Target Page"]
640
638
  df[bool_cols] = df[bool_cols].astype(bool)
641
639
 
642
- df["Page URL"] = (
643
- f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{df['Page Name']}"
640
+ df["Page URL"] = df["Page Name"].apply(
641
+ lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{page_name}"
644
642
  )
645
643
 
646
644
  return df
645
+ # return df.style.format({"Page URL": make_clickable})
647
646
 
648
647
  def list_visuals(self) -> pd.DataFrame:
649
648
  """
@@ -1306,9 +1305,8 @@ class ReportWrapper:
1306
1305
  ----------
1307
1306
  theme_file_path : str
1308
1307
  The file path of the theme.json file. This can either be from a Fabric lakehouse or from the web.
1309
- Examples:
1310
- file_path = '/lakehouse/default/Files/CY23SU09.json'
1311
- file_path = 'https://raw.githubusercontent.com/PowerBiDevCamp/FabricUserApiDemo/main/FabricUserApiDemo/DefinitionTemplates/Shared/Reports/StaticResources/SharedResources/BaseThemes/CY23SU08.json'
1308
+ Example for lakehouse: file_path = '/lakehouse/default/Files/CY23SU09.json'
1309
+ Example for web url: file_path = 'https://raw.githubusercontent.com/PowerBiDevCamp/FabricUserApiDemo/main/FabricUserApiDemo/DefinitionTemplates/Shared/Reports/StaticResources/SharedResources/BaseThemes/CY23SU08.json'
1312
1310
  """
1313
1311
 
1314
1312
  import requests
sempy_labs/tom/_model.py CHANGED
@@ -3,7 +3,10 @@ import sempy.fabric as fabric
3
3
  import pandas as pd
4
4
  import re
5
5
  from datetime import datetime
6
- from sempy_labs._helper_functions import format_dax_object_name
6
+ from sempy_labs._helper_functions import (
7
+ format_dax_object_name,
8
+ generate_guid,
9
+ )
7
10
  from sempy_labs._list_functions import list_relationships
8
11
  from sempy_labs._refresh_semantic_model import refresh_semantic_model
9
12
  from sempy_labs.directlake._dl_helper import check_fallback_reason
@@ -11,6 +14,7 @@ from contextlib import contextmanager
11
14
  from typing import List, Iterator, Optional, Union, TYPE_CHECKING
12
15
  from sempy._utils._log import log
13
16
  import sempy_labs._icons as icons
17
+ from sempy.fabric.exceptions import FabricHTTPException
14
18
 
15
19
  if TYPE_CHECKING:
16
20
  import Microsoft.AnalysisServices.Tabular
@@ -226,7 +230,7 @@ class TOMWrapper:
226
230
  measure_name: str,
227
231
  expression: str,
228
232
  format_string: Optional[str] = None,
229
- hidden: Optional[bool] = False,
233
+ hidden: bool = False,
230
234
  description: Optional[str] = None,
231
235
  display_folder: Optional[str] = None,
232
236
  format_string_expression: Optional[str] = None,
@@ -283,8 +287,12 @@ class TOMWrapper:
283
287
  obj.FormatStringDefinition = fsd
284
288
  if lineage_tag is not None:
285
289
  obj.LineageTag = lineage_tag
290
+ else:
291
+ obj.LineageTag = generate_guid()
286
292
  if source_lineage_tag is not None:
287
293
  obj.SourceLineageTag = source_lineage_tag
294
+ else:
295
+ obj.SourceLineageTag = generate_guid()
288
296
  if detail_rows_expression is not None:
289
297
  drd = TOM.DetailRowsDefinition()
290
298
  drd.Expression = detail_rows_expression
@@ -301,11 +309,11 @@ class TOMWrapper:
301
309
  source_column: str,
302
310
  data_type: str,
303
311
  format_string: Optional[str] = None,
304
- hidden: Optional[bool] = False,
312
+ hidden: bool = False,
305
313
  description: Optional[str] = None,
306
314
  display_folder: Optional[str] = None,
307
315
  data_category: Optional[str] = None,
308
- key: Optional[bool] = False,
316
+ key: bool = False,
309
317
  summarize_by: Optional[str] = None,
310
318
  lineage_tag: Optional[str] = None,
311
319
  source_lineage_tag: Optional[str] = None,
@@ -376,8 +384,12 @@ class TOMWrapper:
376
384
  obj.DataCategory = data_category
377
385
  if lineage_tag is not None:
378
386
  obj.LineageTag = lineage_tag
387
+ else:
388
+ obj.LineageTag = generate_guid()
379
389
  if source_lineage_tag is not None:
380
390
  obj.SourceLineageTag = source_lineage_tag
391
+ else:
392
+ obj.SourceLineageTag = generate_guid()
381
393
  self.model.Tables[table_name].Columns.Add(obj)
382
394
 
383
395
  def add_data_column(
@@ -387,11 +399,11 @@ class TOMWrapper:
387
399
  source_column: str,
388
400
  data_type: str,
389
401
  format_string: Optional[str] = None,
390
- hidden: Optional[bool] = False,
402
+ hidden: bool = False,
391
403
  description: Optional[str] = None,
392
404
  display_folder: Optional[str] = None,
393
405
  data_category: Optional[str] = None,
394
- key: Optional[bool] = False,
406
+ key: bool = False,
395
407
  summarize_by: Optional[str] = None,
396
408
  lineage_tag: Optional[str] = None,
397
409
  source_lineage_tag: Optional[str] = None,
@@ -462,8 +474,12 @@ class TOMWrapper:
462
474
  obj.DataCategory = data_category
463
475
  if lineage_tag is not None:
464
476
  obj.LineageTag = lineage_tag
477
+ else:
478
+ obj.LineageTag = generate_guid()
465
479
  if source_lineage_tag is not None:
466
480
  obj.SourceLineageTag = source_lineage_tag
481
+ else:
482
+ obj.SourceLineagetTag = generate_guid()
467
483
  self.model.Tables[table_name].Columns.Add(obj)
468
484
 
469
485
  def add_calculated_column(
@@ -473,11 +489,11 @@ class TOMWrapper:
473
489
  expression: str,
474
490
  data_type: str,
475
491
  format_string: Optional[str] = None,
476
- hidden: Optional[bool] = False,
492
+ hidden: bool = False,
477
493
  description: Optional[str] = None,
478
494
  display_folder: Optional[str] = None,
479
495
  data_category: Optional[str] = None,
480
- key: Optional[bool] = False,
496
+ key: bool = False,
481
497
  summarize_by: Optional[str] = None,
482
498
  lineage_tag: Optional[str] = None,
483
499
  source_lineage_tag: Optional[str] = None,
@@ -548,8 +564,12 @@ class TOMWrapper:
548
564
  obj.DataCategory = data_category
549
565
  if lineage_tag is not None:
550
566
  obj.LineageTag = lineage_tag
567
+ else:
568
+ obj.LineageTag = generate_guid()
551
569
  if source_lineage_tag is not None:
552
570
  obj.SourceLineageTag = source_lineage_tag
571
+ else:
572
+ obj.SourceLineagetTag = generate_guid()
553
573
  self.model.Tables[table_name].Columns.Add(obj)
554
574
 
555
575
  def add_calculation_item(
@@ -708,7 +728,7 @@ class TOMWrapper:
708
728
  columns: List[str],
709
729
  levels: Optional[List[str]] = None,
710
730
  hierarchy_description: Optional[str] = None,
711
- hierarchy_hidden: Optional[bool] = False,
731
+ hierarchy_hidden: bool = False,
712
732
  lineage_tag: Optional[str] = None,
713
733
  source_lineage_tag: Optional[str] = None,
714
734
  ):
@@ -761,8 +781,12 @@ class TOMWrapper:
761
781
  obj.Description = hierarchy_description
762
782
  if lineage_tag is not None:
763
783
  obj.LineageTag = lineage_tag
784
+ else:
785
+ obj.LineageTag = generate_guid()
764
786
  if source_lineage_tag is not None:
765
787
  obj.SourceLineageTag = source_lineage_tag
788
+ else:
789
+ obj.SourceLineagetTag = generate_guid()
766
790
  self.model.Tables[table_name].Hierarchies.Add(obj)
767
791
 
768
792
  for col in columns:
@@ -770,6 +794,8 @@ class TOMWrapper:
770
794
  lvl.Column = self.model.Tables[table_name].Columns[col]
771
795
  lvl.Name = levels[columns.index(col)]
772
796
  lvl.Ordinal = columns.index(col)
797
+ lvl.LineageTag = generate_guid()
798
+ lvl.SourceLineageTag = generate_guid()
773
799
  self.model.Tables[table_name].Hierarchies[hierarchy_name].Levels.Add(lvl)
774
800
 
775
801
  def add_relationship(
@@ -781,9 +807,9 @@ class TOMWrapper:
781
807
  from_cardinality: str,
782
808
  to_cardinality: str,
783
809
  cross_filtering_behavior: Optional[str] = None,
784
- is_active: Optional[bool] = True,
810
+ is_active: bool = True,
785
811
  security_filtering_behavior: Optional[str] = None,
786
- rely_on_referential_integrity: Optional[bool] = False,
812
+ rely_on_referential_integrity: bool = False,
787
813
  ):
788
814
  """
789
815
  Adds a `relationship <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.singlecolumnrelationship?view=analysisservices-dotnet>`_ to a semantic model.
@@ -855,7 +881,7 @@ class TOMWrapper:
855
881
  name: str,
856
882
  precedence: int,
857
883
  description: Optional[str] = None,
858
- hidden: Optional[bool] = False,
884
+ hidden: bool = False,
859
885
  ):
860
886
  """
861
887
  Adds a `calculation group <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.calculationgroup?view=analysisservices-dotnet>`_ to a semantic model.
@@ -939,8 +965,12 @@ class TOMWrapper:
939
965
  exp.Description = description
940
966
  if lineage_tag is not None:
941
967
  exp.LineageTag = lineage_tag
968
+ else:
969
+ exp.LineageTag = generate_guid()
942
970
  if source_lineage_tag is not None:
943
971
  exp.SourceLineageTag = source_lineage_tag
972
+ else:
973
+ exp.SourceLineageTag = generate_guid()
944
974
  exp.Kind = TOM.ExpressionKind.M
945
975
  exp.Expression = expression
946
976
 
@@ -1034,7 +1064,7 @@ class TOMWrapper:
1034
1064
  entity_name: str,
1035
1065
  expression: Optional[str] = None,
1036
1066
  description: Optional[str] = None,
1037
- schema_name: Optional[str] = None,
1067
+ schema_name: str = "dbo",
1038
1068
  ):
1039
1069
  """
1040
1070
  Adds an entity partition to a table within a semantic model.
@@ -1050,7 +1080,7 @@ class TOMWrapper:
1050
1080
  Defaults to None which resolves to the 'DatabaseQuery' expression.
1051
1081
  description : str, default=None
1052
1082
  A description for the partition.
1053
- schema_name : str, default=None
1083
+ schema_name : str, default="dbo"
1054
1084
  The schema name.
1055
1085
  """
1056
1086
  import Microsoft.AnalysisServices.Tabular as TOM
@@ -1062,8 +1092,7 @@ class TOMWrapper:
1062
1092
  ep.ExpressionSource = self.model.Expressions["DatabaseQuery"]
1063
1093
  else:
1064
1094
  ep.ExpressionSource = self.model.Expressions[expression]
1065
- if schema_name is not None:
1066
- ep.SchemaName = schema_name
1095
+ ep.SchemaName = schema_name
1067
1096
  p = TOM.Partition()
1068
1097
  p.Name = table_name
1069
1098
  p.Source = ep
@@ -1072,6 +1101,9 @@ class TOMWrapper:
1072
1101
  p.Description = description
1073
1102
 
1074
1103
  self.model.Tables[table_name].Partitions.Add(p)
1104
+ self.model.Tables[table_name].SourceLineageTag = (
1105
+ f"[{schema_name}].[{entity_name}]"
1106
+ )
1075
1107
 
1076
1108
  def set_alternate_of(
1077
1109
  self,
@@ -2483,7 +2515,7 @@ class TOMWrapper:
2483
2515
  )
2484
2516
 
2485
2517
  def set_is_available_in_mdx(
2486
- self, table_name: str, column_name: str, value: Optional[bool] = False
2518
+ self, table_name: str, column_name: str, value: bool = False
2487
2519
  ):
2488
2520
  """
2489
2521
  Sets the `IsAvailableInMDX <https://learn.microsoft.com/dotnet/api/microsoft.analysisservices.tabular.column.isavailableinmdx?view=analysisservices-dotnet#microsoft-analysisservices-tabular-column-isavailableinmdx>`_ property on a column.
@@ -2586,7 +2618,7 @@ class TOMWrapper:
2586
2618
  name: str,
2587
2619
  description: Optional[str] = None,
2588
2620
  data_category: Optional[str] = None,
2589
- hidden: Optional[bool] = False,
2621
+ hidden: bool = False,
2590
2622
  lineage_tag: Optional[str] = None,
2591
2623
  source_lineage_tag: Optional[str] = None,
2592
2624
  ):
@@ -2618,8 +2650,12 @@ class TOMWrapper:
2618
2650
  t.DataCategory = data_category
2619
2651
  if lineage_tag is not None:
2620
2652
  t.LineageTag = lineage_tag
2653
+ else:
2654
+ t.LineageTag = generate_guid()
2621
2655
  if source_lineage_tag is not None:
2622
2656
  t.SourceLineageTag = source_lineage_tag
2657
+ else:
2658
+ t.SourceLineagetTag = generate_guid()
2623
2659
  t.Hidden = hidden
2624
2660
  self.model.Tables.Add(t)
2625
2661
 
@@ -2629,9 +2665,9 @@ class TOMWrapper:
2629
2665
  expression: str,
2630
2666
  description: Optional[str] = None,
2631
2667
  data_category: Optional[str] = None,
2632
- hidden: Optional[bool] = False,
2633
- lineage_tag: Optional[bool] = None,
2634
- source_lineage_tag: Optional[bool] = None,
2668
+ hidden: bool = False,
2669
+ lineage_tag: Optional[str] = None,
2670
+ source_lineage_tag: Optional[str] = None,
2635
2671
  ):
2636
2672
  """
2637
2673
  Adds a calculated table to the semantic model.
@@ -2670,8 +2706,12 @@ class TOMWrapper:
2670
2706
  t.DataCategory = data_category
2671
2707
  if lineage_tag is not None:
2672
2708
  t.LineageTag = lineage_tag
2709
+ else:
2710
+ t.LineageTag = generate_guid()
2673
2711
  if source_lineage_tag is not None:
2674
2712
  t.SourceLineageTag = source_lineage_tag
2713
+ else:
2714
+ t.SourceLineagetTag = generate_guid()
2675
2715
  t.Hidden = hidden
2676
2716
  t.Partitions.Add(par)
2677
2717
  self.model.Tables.Add(t)
@@ -3378,7 +3418,7 @@ class TOMWrapper:
3378
3418
  incremental_periods: int,
3379
3419
  rolling_window_granularity: str,
3380
3420
  rolling_window_periods: int,
3381
- only_refresh_complete_days: Optional[bool] = False,
3421
+ only_refresh_complete_days: bool = False,
3382
3422
  detect_data_changes_column: Optional[str] = None,
3383
3423
  ):
3384
3424
  """
@@ -3483,7 +3523,7 @@ class TOMWrapper:
3483
3523
  incremental_periods: int,
3484
3524
  rolling_window_granularity: str,
3485
3525
  rolling_window_periods: int,
3486
- only_refresh_complete_days: Optional[bool] = False,
3526
+ only_refresh_complete_days: bool = False,
3487
3527
  detect_data_changes_column: Optional[str] = None,
3488
3528
  ):
3489
3529
  """
@@ -3652,7 +3692,7 @@ class TOMWrapper:
3652
3692
  self,
3653
3693
  table_name: str,
3654
3694
  effective_date: Optional[datetime] = None,
3655
- refresh: Optional[bool] = True,
3695
+ refresh: bool = True,
3656
3696
  max_parallelism: Optional[int] = 0,
3657
3697
  ):
3658
3698
  """
@@ -3970,6 +4010,8 @@ class TOMWrapper:
3970
4010
  data_category: Optional[str] = None,
3971
4011
  key: Optional[bool] = None,
3972
4012
  summarize_by: Optional[str] = None,
4013
+ is_nullable: Optional[bool] = None,
4014
+ is_available_in_mdx: Optional[bool] = None,
3973
4015
  ):
3974
4016
  """
3975
4017
  Updates a column within a semantic model.
@@ -4010,6 +4052,10 @@ class TOMWrapper:
4010
4052
  summarize_by : str, default=None
4011
4053
  Sets the value for the Summarize By property of the column.
4012
4054
  Defaults to None which keeps the existing setting.
4055
+ is_nullable : bool, default=None
4056
+ If False, the column cannot contain nulls. Even if True, it may still not allow nulls if it's a key column.
4057
+ is_available_in_mdx : bool, default=None
4058
+ A boolean value that indicates whether the column can be excluded from usage in MDX query tools. False if the column can be excluded from usage in MDX query tools; otherwise true.
4013
4059
  """
4014
4060
 
4015
4061
  import Microsoft.AnalysisServices.Tabular as TOM
@@ -4038,6 +4084,10 @@ class TOMWrapper:
4038
4084
  c.DataCategory = data_category
4039
4085
  if summarize_by is not None:
4040
4086
  c.SummarizeBy = System.Enum.Parse(TOM.AggregateFunction, summarize_by)
4087
+ if is_nullable is not None:
4088
+ c.IsNullable = is_nullable
4089
+ if is_available_in_mdx is not None:
4090
+ c.IsAvailableInMDX = is_available_in_mdx
4041
4091
 
4042
4092
  def update_role(
4043
4093
  self,
@@ -4160,6 +4210,28 @@ class TOMWrapper:
4160
4210
 
4161
4211
  self.model.Tables[table_name].Columns[column_name].SortByColumn = None
4162
4212
 
4213
+ def is_calculated_column(self, table_name: str, column_name: str):
4214
+ """
4215
+ Identifies if a column is a calculated column.
4216
+
4217
+ Parameters
4218
+ ----------
4219
+ table_name : str
4220
+ Name of the table in which the column resides.
4221
+ column_name : str
4222
+ Name of the column.
4223
+
4224
+ Returns
4225
+ -------
4226
+ bool
4227
+ A boolean value indicating whether the column is a calculated column.
4228
+ """
4229
+
4230
+ import Microsoft.AnalysisServices.Tabular as TOM
4231
+
4232
+ c = self.model.Tables[table_name].Columns[column_name]
4233
+ return c.Type == TOM.ColumnType.Calculated
4234
+
4163
4235
  def is_calculated_table(self, table_name: str):
4164
4236
  """
4165
4237
  Identifies if a table is a calculated table.
@@ -4186,6 +4258,171 @@ class TOMWrapper:
4186
4258
  isCalcTable = True
4187
4259
  return isCalcTable
4188
4260
 
4261
+ def update_lineage_tags(self):
4262
+ """
4263
+ Adds lineage and source lineage tags for relevant semantic model objects if they do not exist. Also updates schema name for Direct Lake (entity) partitions.
4264
+ """
4265
+
4266
+ import Microsoft.AnalysisServices.Tabular as TOM
4267
+
4268
+ for t in self.model.Tables:
4269
+ if len(t.LineageTag) == 0:
4270
+ t.LineageTag = generate_guid()
4271
+ if len(t.SourceLineageTag) == 0:
4272
+ if next(p.Mode for p in t.Partitions) == TOM.ModeType.DirectLake:
4273
+ partition_name = next(p.Name for p in t.Partitions)
4274
+ entity_name = t.Partitions[partition_name].Source.EntityName
4275
+ schema_name = t.Partitions[partition_name].Source.SchemaName
4276
+
4277
+ # Update schema name and source lineage tag for DL (entity) partitions
4278
+ if len(schema_name) == 0:
4279
+ schema_name = icons.default_schema
4280
+ t.Partitions[partition_name].Source.SchemaName = (
4281
+ icons.default_schema
4282
+ )
4283
+ t.SourceLineageTag = f"[{schema_name}].[{entity_name}]"
4284
+ else:
4285
+ t.SourceLineageTag = generate_guid()
4286
+ for c in self.all_columns():
4287
+ if len(c.LineageTag) == 0:
4288
+ c.LineageTag = generate_guid()
4289
+ if len(c.SourceLineageTag) == 0:
4290
+ c.SourceLineageTag = generate_guid()
4291
+ for m in self.all_measures():
4292
+ if len(m.LineageTag) == 0:
4293
+ m.LineageTag = generate_guid()
4294
+ if len(m.SourceLineageTag) == 0:
4295
+ m.SourceLineageTag = generate_guid()
4296
+ for h in self.all_hierarchies():
4297
+ if len(h.LineageTag) == 0:
4298
+ h.LineageTag = generate_guid()
4299
+ if len(h.SourceLineageTag) == 0:
4300
+ h.SourceLineageTag = generate_guid()
4301
+ for lvl in self.all_levels():
4302
+ if len(lvl.LineageTag) == 0:
4303
+ lvl.LineageTag = generate_guid()
4304
+ if len(lvl.SourceLineageTag) == 0:
4305
+ lvl.SourceLineageTag = generate_guid()
4306
+ for e in self.model.Expressions():
4307
+ if len(e.LineageTag) == 0:
4308
+ e.LineageTag = generate_guid()
4309
+ if len(e.SourceLineageTag) == 0:
4310
+ e.SourceLineageTag = generate_guid()
4311
+
4312
+ def generate_measure_descriptions(
4313
+ self,
4314
+ measure_name: Optional[str | List[str]] = None,
4315
+ max_batch_size: Optional[int] = 5,
4316
+ ):
4317
+ """
4318
+ Auto-generates descriptions for measures using an LLM.
4319
+
4320
+ Parameters
4321
+ ----------
4322
+ measure_name : str | List[str], default=None
4323
+ The measure name (or a list of measure names).
4324
+ Defaults to None which generates descriptions for all measures in the semantic model.
4325
+ max_batch_size : int, default=5
4326
+ Sets the max batch size for each API call.
4327
+ """
4328
+
4329
+ # import concurrent.futures
4330
+
4331
+ if isinstance(measure_name, str):
4332
+ measure_name = [measure_name]
4333
+
4334
+ workspace_id = fabric.resolve_workspace_id(self._workspace)
4335
+ client = fabric.FabricRestClient()
4336
+
4337
+ if len(measure_name) > max_batch_size:
4338
+ measure_lists = [
4339
+ measure_name[i : i + max_batch_size]
4340
+ for i in range(0, len(measure_name), max_batch_size)
4341
+ ]
4342
+ else:
4343
+ measure_lists = [measure_name]
4344
+
4345
+ # Each API call can have a max of 5 measures
4346
+ for measure_list in measure_lists:
4347
+ payload = {
4348
+ "scenarioDefinition": {
4349
+ "generateModelItemDescriptions": {
4350
+ "modelItems": [],
4351
+ },
4352
+ },
4353
+ "workspaceId": workspace_id,
4354
+ "artifactInfo": {"artifactType": "SemanticModel"},
4355
+ }
4356
+ for m_name in measure_list:
4357
+ expr, t_name = next(
4358
+ (ms.Expression, ms.Parent.Name)
4359
+ for ms in self.all_measures()
4360
+ if ms.Name == m_name
4361
+ )
4362
+ if t_name is None:
4363
+ raise ValueError(
4364
+ f"{icons.red_dot} The '{m_name}' measure does not exist in the '{self._dataset}' semantic model within the '{self._workspace}' workspace."
4365
+ )
4366
+
4367
+ new_item = {
4368
+ "urn": m_name,
4369
+ "type": 1,
4370
+ "name": m_name,
4371
+ "expression": expr,
4372
+ }
4373
+ payload["scenarioDefinition"]["generateModelItemDescriptions"][
4374
+ "modelItems"
4375
+ ].append(new_item)
4376
+
4377
+ response = client.post("/explore/v202304/nl2nl/completions", json=payload)
4378
+ if response.status_code != 200:
4379
+ raise FabricHTTPException(response)
4380
+
4381
+ for item in response.json().get("modelItems", []):
4382
+ ms_name = item["urn"]
4383
+ if ms_name.startswith("urn: "):
4384
+ ms_name = ms_name[5:]
4385
+ desc = item.get("description")
4386
+ table_name = next(
4387
+ m.Parent.Name for m in self.all_measures() if m.Name == ms_name
4388
+ )
4389
+ self.model.Tables[table_name].Measures[ms_name].Description = desc
4390
+
4391
+ # def process_measure(m):
4392
+ # table_name = m.Parent.Name
4393
+ # m_name = m.Name
4394
+ # m_name_fixed = "1"
4395
+ # expr = m.Expression
4396
+ # if measure_name is None or m_name in measure_name:
4397
+ # payload = {
4398
+ # "scenarioDefinition": {
4399
+ # "generateModelItemDescriptions": {
4400
+ # "modelItems": [
4401
+ # {
4402
+ # "urn": f"modelobject://Table/{table_name}/Measure/{m_name_fixed}",
4403
+ # "type": 1,
4404
+ # "name": m_name,
4405
+ # "expression": expr,
4406
+ # }
4407
+ # ]
4408
+ # }
4409
+ # },
4410
+ # "workspaceId": workspace_id,
4411
+ # "artifactInfo": {"artifactType": "SemanticModel"},
4412
+ # }
4413
+
4414
+ # response = client.post(
4415
+ # "/explore/v202304/nl2nl/completions", json=payload
4416
+ # )
4417
+ # if response.status_code != 200:
4418
+ # raise FabricHTTPException(response)
4419
+
4420
+ # desc = response.json()["modelItems"][0]["description"]
4421
+ # m.Description = desc
4422
+
4423
+ # with concurrent.futures.ThreadPoolExecutor() as executor:
4424
+ # executor.map(process_measure, self.all_measures())
4425
+
4189
4426
  def close(self):
4190
4427
  if not self._readonly and self.model is not None:
4191
4428
  self.model.SaveChanges()