From 25feab83531efb416bb93dd83dfc0edf10bf46af Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 09:01:45 +0200 Subject: [PATCH 1/6] fixed list_dataflows --- src/sempy_labs/_dataflows.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sempy_labs/_dataflows.py b/src/sempy_labs/_dataflows.py index eda87a2..2055879 100644 --- a/src/sempy_labs/_dataflows.py +++ b/src/sempy_labs/_dataflows.py @@ -37,20 +37,21 @@ def list_dataflows(workspace: Optional[str] = None): columns=["Dataflow Id", "Dataflow Name", "Configured By", "Users", "Generation"] ) + data = [] # Collect rows here + for v in response.json().get("value", []): new_data = { "Dataflow Id": v.get("objectId"), "Dataflow Name": v.get("name"), "Configured By": v.get("configuredBy"), - "Users": v.get("users"), + "Users": v.get("users", []), "Generation": v.get("generation"), } - df = pd.concat( - [df, pd.DataFrame(new_data, index=[0])], - ignore_index=True, - ) + data.append(new_data) - df["Generation"] = df["Generation"].astype(int) + if data: + df = pd.DataFrame(data) + df["Generation"] = df["Generation"].astype(int) return df From f02be28990bea3eeac9b3369d82050345d6a6599 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 09:41:33 +0200 Subject: [PATCH 2/6] or UUID --- src/sempy_labs/_clear_cache.py | 72 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/sempy_labs/_clear_cache.py b/src/sempy_labs/_clear_cache.py index fdcd62e..d34a85a 100644 --- a/src/sempy_labs/_clear_cache.py +++ b/src/sempy_labs/_clear_cache.py @@ -1,39 +1,41 @@ import sempy.fabric as fabric from sempy_labs._helper_functions import ( - resolve_dataset_id, is_default_semantic_model, _get_adls_client, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, ) from typing import Optional import sempy_labs._icons as icons from sempy._utils._log import log import pandas as pd from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def clear_cache(dataset: str, workspace: Optional[str] = None): +def clear_cache(dataset: str | UUID, workspace: Optional[str | UUID] = None): """ Clears the cache of a semantic model. See `here `_ for documentation. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if is_default_semantic_model(dataset=dataset, workspace=workspace): raise ValueError( f"{icons.red_dot} Cannot run XMLA operations against a default semantic model. Please choose a different semantic model. " "See here for more information: https://learn.microsoft.com/fabric/data-warehouse/semantic-models" ) - dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) xmla = f""" @@ -42,27 +44,27 @@ def clear_cache(dataset: str, workspace: Optional[str] = None): """ - fabric.execute_xmla(dataset=dataset, xmla_command=xmla, workspace=workspace) + fabric.execute_xmla(dataset=dataset_id, xmla_command=xmla, workspace=workspace_id) print( - f"{icons.green_dot} Cache cleared for the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} Cache cleared for the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) @log def backup_semantic_model( - dataset: str, + dataset: str | UUID, file_path: str, allow_overwrite: bool = True, apply_compression: bool = True, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ `Backs up `_ a semantic model to the ADLS Gen2 storage account connected to the workspace. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. file_path : str The ADLS Gen2 storage account location in which to backup the semantic model. Always saves within the 'power-bi-backup/' folder. Must end in '.abf'. @@ -72,8 +74,8 @@ def backup_semantic_model( If True, overwrites backup files of the same name. If False, the file you are saving cannot have the same name as a file that already exists in the same location. apply_compression : bool, default=True If True, compresses the backup file. Compressed backup files save disk space, but require slightly higher CPU utilization. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -83,31 +85,32 @@ def backup_semantic_model( f"{icons.red_dot} The backup file for restoring must be in the .abf format." ) - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) tmsl = { "backup": { - "database": dataset, + "database": dataset_name, "file": file_path, "allowOverwrite": allow_overwrite, "applyCompression": apply_compression, } } - fabric.execute_tmsl(script=tmsl, workspace=workspace) + fabric.execute_tmsl(script=tmsl, workspace=workspace_id) print( - f"{icons.green_dot} The '{dataset}' semantic model within the '{workspace}' workspace has been backed up to the '{file_path}' location." + f"{icons.green_dot} The '{dataset_name}' semantic model within the '{workspace_name}' workspace has been backed up to the '{file_path}' location." ) @log def restore_semantic_model( - dataset: str, + dataset: str | UUID, file_path: str, allow_overwrite: bool = True, ignore_incompatibilities: bool = True, force_restore: bool = False, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ `Restores `_ a semantic model based on a backup (.abf) file @@ -115,8 +118,8 @@ def restore_semantic_model( Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. file_path : str The location in which to backup the semantic model. Must end in '.abf'. Example 1: file_path = 'MyModel.abf' @@ -127,23 +130,23 @@ def restore_semantic_model( If True, ignores incompatibilities between Azure Analysis Services and Power BI Premium. force_restore: bool, default=False If True, restores the semantic model with the existing semantic model unloaded and offline. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - # https://learn.microsoft.com/en-us/power-bi/enterprise/service-premium-backup-restore-dataset if not file_path.endswith(".abf"): raise ValueError( f"{icons.red_dot} The backup file for restoring must be in the .abf format." ) - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) tmsl = { "restore": { - "database": dataset, + "database": dataset_name, "file": file_path, "allowOverwrite": allow_overwrite, "security": "copyAll", @@ -154,10 +157,10 @@ def restore_semantic_model( if force_restore: tmsl["restore"]["forceRestore"] = force_restore - fabric.execute_tmsl(script=tmsl, workspace=workspace) + fabric.execute_tmsl(script=tmsl, workspace=workspace_id) print( - f"{icons.green_dot} The '{dataset}' semantic model has been restored to the '{workspace}' workspace based on teh '{file_path}' backup file." + f"{icons.green_dot} The '{dataset_name}' semantic model has been restored to the '{workspace_name}' workspace based on the '{file_path}' backup file." ) @@ -243,7 +246,7 @@ def copy_semantic_model_backup_file( @log -def list_backups(workspace: Optional[str] = None) -> pd.DataFrame: +def list_backups(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows a list of backup files contained within a workspace's ADLS Gen2 storage account. Requirement: An ADLS Gen2 storage account must be `connected to the workspace `_. @@ -262,8 +265,7 @@ def list_backups(workspace: Optional[str] = None) -> pd.DataFrame: """ client = fabric.PowerBIRestClient() - workspace = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) response = client.get( f"/v1.0/myorg/resources?resourceType=StorageAccount&folderObjectId={workspace_id}" ) @@ -274,7 +276,7 @@ def list_backups(workspace: Optional[str] = None) -> pd.DataFrame: v = response.json().get("value", []) if not v: raise ValueError( - f"{icons.red_dot} A storage account is not associated with the '{workspace}' workspace." + f"{icons.red_dot} A storage account is not associated with the '{workspace_name}' workspace." ) storage_account = v[0]["resourceName"] From f07c5edbba0c492d2ad27ea65c23d53030ea76df Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 10:27:45 +0200 Subject: [PATCH 3/6] updated vertipaq to use ID or name --- src/sempy_labs/_helper_functions.py | 10 ++--- src/sempy_labs/_vertipaq.py | 57 +++++++++++++------------ src/sempy_labs/directlake/_dl_helper.py | 15 +++---- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index 2ff51e5..5edfab6 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -732,14 +732,14 @@ def _add_part(target_dict, path, payload): target_dict["definition"]["parts"].append(part) -def resolve_workspace_capacity(workspace: Optional[str] = None) -> Tuple[UUID, str]: +def resolve_workspace_capacity(workspace: Optional[str | UUID] = None) -> Tuple[UUID, str]: """ Obtains the capacity Id and capacity name for a given workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or UUID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -749,8 +749,8 @@ def resolve_workspace_capacity(workspace: Optional[str] = None) -> Tuple[UUID, s capacity Id; capacity came. """ - workspace = fabric.resolve_workspace_name(workspace) - filter_condition = urllib.parse.quote(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + filter_condition = urllib.parse.quote(workspace_name) dfW = fabric.list_workspaces(filter=f"name eq '{filter_condition}'") capacity_id = dfW["Capacity Id"].iloc[0] dfC = fabric.list_capacities() diff --git a/src/sempy_labs/_vertipaq.py b/src/sempy_labs/_vertipaq.py index f98c0aa..b032141 100644 --- a/src/sempy_labs/_vertipaq.py +++ b/src/sempy_labs/_vertipaq.py @@ -10,10 +10,11 @@ from sempy_labs._helper_functions import ( format_dax_object_name, resolve_lakehouse_name, - resolve_dataset_id, save_as_delta_table, resolve_workspace_capacity, _get_max_run_id, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id ) from sempy_labs._list_functions import list_relationships, list_tables from sempy_labs.lakehouse import lakehouse_attached, get_lakehouse_tables @@ -22,12 +23,13 @@ from sempy._utils._log import log import sempy_labs._icons as icons from pathlib import Path +from uuid import UUID @log def vertipaq_analyzer( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, export: Optional[str] = None, read_stats_from_data: bool = False, **kwargs, @@ -37,10 +39,10 @@ def vertipaq_analyzer( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name in which the semantic model exists. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str| UUID, default=None + The Fabric workspace name or ID in which the semantic model exists. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. export : str, default=None @@ -68,7 +70,8 @@ def vertipaq_analyzer( "ignore", message="createDataFrame attempted Arrow optimization*" ) - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) vertipaq_map = { "Model": { @@ -135,7 +138,7 @@ def vertipaq_analyzer( } with connect_semantic_model( - dataset=dataset, workspace=workspace, readonly=True + dataset=dataset_id, workspace=workspace_id, readonly=True ) as tom: compat_level = tom.model.Model.Database.CompatibilityLevel is_direct_lake = tom.is_direct_lake() @@ -144,24 +147,24 @@ def vertipaq_analyzer( column_count = len(list(tom.all_columns())) if table_count == 0: print( - f"{icons.warning} The '{dataset}' semantic model within the '{workspace}' workspace has no tables. Vertipaq Analyzer can only be run if the semantic model has tables." + f"{icons.warning} The '{dataset_name}' semantic model within the '{workspace_name}' workspace has no tables. Vertipaq Analyzer can only be run if the semantic model has tables." ) return - dfT = list_tables(dataset=dataset, extended=True, workspace=workspace) + dfT = list_tables(dataset=dataset_id, extended=True, workspace=workspace_id) dfT.rename(columns={"Name": "Table Name"}, inplace=True) columns_to_keep = list(vertipaq_map["Tables"].keys()) dfT = dfT[dfT.columns.intersection(columns_to_keep)] - dfC = fabric.list_columns(dataset=dataset, extended=True, workspace=workspace) + dfC = fabric.list_columns(dataset=dataset_id, extended=True, workspace=workspace_id) dfC["Column Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"]) dfC.rename(columns={"Column Cardinality": "Cardinality"}, inplace=True) - dfH = fabric.list_hierarchies(dataset=dataset, extended=True, workspace=workspace) - dfR = list_relationships(dataset=dataset, extended=True, workspace=workspace) - dfP = fabric.list_partitions(dataset=dataset, extended=True, workspace=workspace) + dfH = fabric.list_hierarchies(dataset=dataset_id, extended=True, workspace=workspace_id) + dfR = list_relationships(dataset=dataset_id, extended=True, workspace=workspace_id) + dfP = fabric.list_partitions(dataset=dataset_id, extended=True, workspace=workspace_id) artifact_type, lakehouse_name, lakehouse_id, lakehouse_workspace_id = ( - get_direct_lake_source(dataset=dataset, workspace=workspace) + get_direct_lake_source(dataset=dataset_id, workspace=workspace_id) ) dfR["Missing Rows"] = 0 @@ -308,7 +311,7 @@ def vertipaq_analyzer( query = f"evaluate\nsummarizecolumns(\n\"1\",calculate(countrows('{fromTable}'),userelationship({fromObject},{toObject}),isblank({toObject}))\n)" result = fabric.evaluate_dax( - dataset=dataset, dax_string=query, workspace=workspace + dataset=dataset_id, dax_string=query, workspace=workspace_id ) try: @@ -407,7 +410,7 @@ def vertipaq_analyzer( dfModel = pd.DataFrame( { - "Dataset Name": dataset, + "Dataset Name": dataset_name, "Total Size": y, "Table Count": table_count, "Column Count": column_count, @@ -532,19 +535,19 @@ def _style_columns_based_on_types(dataframe: pd.DataFrame, column_type_mapping): f"{icons.in_progress} Saving Vertipaq Analyzer to delta tables in the lakehouse...\n" ) now = datetime.datetime.now() - dfD = fabric.list_datasets(workspace=workspace, mode="rest") - dfD_filt = dfD[dfD["Dataset Name"] == dataset] + dfD = fabric.list_datasets(workspace=workspace_id, mode="rest") + dfD_filt = dfD[dfD["Dataset Id"] == dataset_id] configured_by = dfD_filt["Configured By"].iloc[0] - capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace) + capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace_id) for key_name, (obj, df) in dfMap.items(): df["Capacity Name"] = capacity_name df["Capacity Id"] = capacity_id df["Configured By"] = configured_by - df["Workspace Name"] = workspace - df["Workspace Id"] = fabric.resolve_workspace_id(workspace) - df["Dataset Name"] = dataset - df["Dataset Id"] = resolve_dataset_id(dataset, workspace) + df["Workspace Name"] = workspace_name + df["Workspace Id"] = workspace_id + df["Dataset Name"] = dataset_name + df["Dataset Id"] = dataset_id df["RunId"] = runId df["Timestamp"] = now @@ -605,7 +608,7 @@ def _style_columns_based_on_types(dataframe: pd.DataFrame, column_type_mapping): "dfH_filt": dfH_filt, } - zipFileName = f"{workspace}.{dataset}.zip" + zipFileName = f"{workspace_name}.{dataset_name}.zip" folderPath = "/lakehouse/default/Files" subFolderPath = os.path.join(folderPath, "VertipaqAnalyzer") @@ -631,7 +634,7 @@ def _style_columns_based_on_types(dataframe: pd.DataFrame, column_type_mapping): if os.path.exists(filePath): os.remove(filePath) print( - f"{icons.green_dot} The Vertipaq Analyzer info for the '{dataset}' semantic model in the '{workspace}' workspace has been saved " + f"{icons.green_dot} The Vertipaq Analyzer info for the '{dataset_name}' semantic model in the '{workspace_name}' workspace has been saved " f"to the 'Vertipaq Analyzer/{zipFileName}' in the default lakehouse attached to this notebook." ) diff --git a/src/sempy_labs/directlake/_dl_helper.py b/src/sempy_labs/directlake/_dl_helper.py index a539552..a4759a1 100644 --- a/src/sempy_labs/directlake/_dl_helper.py +++ b/src/sempy_labs/directlake/_dl_helper.py @@ -7,7 +7,6 @@ from sempy._utils._log import log from sempy_labs._helper_functions import ( retry, - resolve_dataset_id, resolve_lakehouse_name, _convert_data_type, resolve_dataset_name_and_id, @@ -203,17 +202,17 @@ def dyn_connect(): def get_direct_lake_source( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> Tuple[str, str, UUID, UUID]: """ Obtains the source information for a direct lake semantic model. Parameters ---------- - dataset : str - The name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + The name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -225,8 +224,8 @@ def get_direct_lake_source( If the semantic model is not a Direct Lake semantic model, it will return None, None, None. """ - workspace = fabric.resolve_workspace_name(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) client = fabric.PowerBIRestClient() request_body = { "artifacts": [ From 92ad6ac914697b84754623a72b973e502fbb2b8b Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 12:26:49 +0200 Subject: [PATCH 4/6] update UUID --- src/sempy_labs/_connections.py | 12 +- src/sempy_labs/_generate_semantic_model.py | 17 +- .../directlake/_directlake_schema_compare.py | 32 ++-- .../directlake/_directlake_schema_sync.py | 30 ++-- src/sempy_labs/directlake/_dl_helper.py | 28 +-- .../directlake/_generate_shared_expression.py | 19 +- .../directlake/_get_directlake_lakehouse.py | 29 +-- .../directlake/_get_shared_expression.py | 7 +- src/sempy_labs/directlake/_guardrails.py | 16 +- .../_list_directlake_model_calc_tables.py | 24 ++- .../_show_unsupported_directlake_objects.py | 27 +-- ...e_directlake_model_lakehouse_connection.py | 64 +++---- .../_update_directlake_partition_entity.py | 62 ++++--- src/sempy_labs/directlake/_warm_cache.py | 65 ++++--- .../lakehouse/_get_lakehouse_columns.py | 18 +- .../lakehouse/_get_lakehouse_tables.py | 19 +- src/sempy_labs/lakehouse/_lakehouse.py | 30 ++-- src/sempy_labs/lakehouse/_shortcuts.py | 53 +++--- src/sempy_labs/migration/_create_pqt_file.py | 28 +-- .../_migrate_calctables_to_lakehouse.py | 1 - .../migration/_refresh_calc_tables.py | 26 +-- src/sempy_labs/report/_download_report.py | 17 +- src/sempy_labs/report/_generate_report.py | 82 ++++----- src/sempy_labs/report/_paginated.py | 18 +- src/sempy_labs/report/_report_bpa.py | 22 ++- src/sempy_labs/report/_report_functions.py | 169 ++++++++---------- src/sempy_labs/report/_report_helper.py | 12 +- .../report/_report_list_functions.py | 35 ++-- src/sempy_labs/report/_report_rebind.py | 32 ++-- src/sempy_labs/report/_reportwrapper.py | 66 +++---- 30 files changed, 564 insertions(+), 496 deletions(-) diff --git a/src/sempy_labs/_connections.py b/src/sempy_labs/_connections.py index 5358da3..b647d2d 100644 --- a/src/sempy_labs/_connections.py +++ b/src/sempy_labs/_connections.py @@ -5,6 +5,7 @@ from sempy_labs._helper_functions import ( pagination, _is_valid_uuid, + resolve_workspace_name_and_id, ) from uuid import UUID import sempy_labs._icons as icons @@ -205,7 +206,7 @@ def list_connections() -> pd.DataFrame: def list_item_connections( - item_name: str, item_type: str, workspace: Optional[str] = None + item_name: str, item_type: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows the list of connections that the specified item is connected to. @@ -218,8 +219,8 @@ def list_item_connections( The item name. item_type : str The `item type `_. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -229,11 +230,10 @@ def list_item_connections( A pandas dataframe showing the list of connections that the specified item is connected to. """ - workspace = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_type = item_type[0].upper() + item_type[1:] item_id = fabric.resolve_item_id( - item_name=item_name, type=item_type, workspace=workspace + item_name=item_name, type=item_type, workspace=workspace_id ) client = fabric.FabricRestClient() diff --git a/src/sempy_labs/_generate_semantic_model.py b/src/sempy_labs/_generate_semantic_model.py index 6a2d655..e1ecfa5 100644 --- a/src/sempy_labs/_generate_semantic_model.py +++ b/src/sempy_labs/_generate_semantic_model.py @@ -14,12 +14,13 @@ from sempy_labs.lakehouse._lakehouse import lakehouse_attached import sempy_labs._icons as icons from sempy_labs._refresh_semantic_model import refresh_semantic_model +from uuid import UUID def create_blank_semantic_model( dataset: str, compatibility_level: int = 1605, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, overwrite: bool = True, ): """ @@ -31,21 +32,21 @@ def create_blank_semantic_model( Name of the semantic model. compatibility_level : int, default=1605 The compatibility level of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. overwrite : bool, default=False If set to True, overwrites the existing semantic model in the workspace if it exists. """ - workspace = fabric.resolve_workspace_name(workspace) - dfD = fabric.list_datasets(workspace=workspace, mode="rest") + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + dfD = fabric.list_datasets(workspace=workspace_id, mode="rest") dfD_filt = dfD[dfD["Dataset Name"] == dataset] if len(dfD_filt) > 0 and not overwrite: raise ValueError( - f"{icons.warning} The '{dataset}' semantic model already exists within the '{workspace}' workspace. The 'overwrite' parameter is set to False so the blank new semantic model was not created." + f"{icons.warning} The '{dataset}' semantic model already exists within the '{workspace_name}' workspace. The 'overwrite' parameter is set to False so the blank new semantic model was not created." ) min_compat = 1500 @@ -109,10 +110,10 @@ def create_blank_semantic_model( }} """ - fabric.execute_tmsl(script=tmsl, workspace=workspace) + fabric.execute_tmsl(script=tmsl, workspace=workspace_id) return print( - f"{icons.green_dot} The '{dataset}' semantic model was created within the '{workspace}' workspace." + f"{icons.green_dot} The '{dataset}' semantic model was created within the '{workspace_name}' workspace." ) diff --git a/src/sempy_labs/directlake/_directlake_schema_compare.py b/src/sempy_labs/directlake/_directlake_schema_compare.py index 9c06e22..37dd616 100644 --- a/src/sempy_labs/directlake/_directlake_schema_compare.py +++ b/src/sempy_labs/directlake/_directlake_schema_compare.py @@ -2,6 +2,8 @@ import pandas as pd from sempy_labs._helper_functions import ( format_dax_object_name, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, ) from IPython.display import display from sempy_labs.lakehouse import get_lakehouse_columns @@ -9,12 +11,13 @@ from typing import Optional import sempy_labs._icons as icons from sempy._utils._log import log +from uuid import UUID @log def direct_lake_schema_compare( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, **kwargs, ): """ @@ -22,10 +25,10 @@ def direct_lake_schema_compare( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -41,10 +44,11 @@ def direct_lake_schema_compare( ) del kwargs["lakehouse_workspace"] - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) artifact_type, lakehouse_name, lakehouse_id, lakehouse_workspace_id = ( - get_direct_lake_source(dataset=dataset, workspace=workspace) + get_direct_lake_source(dataset=dataset_id, workspace=workspace_id) ) lakehouse_workspace = fabric.resolve_workspace_name(lakehouse_workspace_id) @@ -53,15 +57,15 @@ def direct_lake_schema_compare( f"{icons.red_dot} This function is only valid for Direct Lake semantic models which source from Fabric lakehouses (not warehouses)." ) - dfP = fabric.list_partitions(dataset=dataset, workspace=workspace) + dfP = fabric.list_partitions(dataset=dataset_id, workspace=workspace_id) if not any(r["Mode"] == "DirectLake" for i, r in dfP.iterrows()): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model is not in Direct Lake mode." + f"{icons.red_dot} The '{dataset_name}' semantic model within the '{workspace_name}' workspace is not in Direct Lake mode." ) - dfT = fabric.list_tables(dataset=dataset, workspace=workspace) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace) + dfT = fabric.list_tables(dataset=dataset_id, workspace=workspace_id) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id) lc = get_lakehouse_columns(lakehouse_name, lakehouse_workspace) dfT.rename(columns={"Type": "Table Type"}, inplace=True) @@ -92,7 +96,7 @@ def direct_lake_schema_compare( ) else: print( - f"{icons.yellow_dot} The following tables exist in the '{dataset}' semantic model within the '{workspace}' workspace" + f"{icons.yellow_dot} The following tables exist in the '{dataset_name}' semantic model within the '{workspace_name}' workspace" f" but do not exist in the '{lakehouse_name}' lakehouse within the '{lakehouse_workspace}' workspace." ) display(missingtbls) @@ -102,7 +106,7 @@ def direct_lake_schema_compare( ) else: print( - f"{icons.yellow_dot} The following columns exist in the '{dataset}' semantic model within the '{workspace}' workspace " + f"{icons.yellow_dot} The following columns exist in the '{dataset_name}' semantic model within the '{workspace_name}' workspace " f"but do not exist in the '{lakehouse_name}' lakehouse within the '{lakehouse_workspace}' workspace." ) display(missingcols) diff --git a/src/sempy_labs/directlake/_directlake_schema_sync.py b/src/sempy_labs/directlake/_directlake_schema_sync.py index 024b04e..1c7d281 100644 --- a/src/sempy_labs/directlake/_directlake_schema_sync.py +++ b/src/sempy_labs/directlake/_directlake_schema_sync.py @@ -3,16 +3,21 @@ from sempy_labs.lakehouse import get_lakehouse_columns from sempy_labs.directlake._dl_helper import get_direct_lake_source from sempy_labs.tom import connect_semantic_model -from sempy_labs._helper_functions import _convert_data_type +from sempy_labs._helper_functions import ( + _convert_data_type, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, +) from typing import Optional from sempy._utils._log import log import sempy_labs._icons as icons +from uuid import UUID @log def direct_lake_schema_sync( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, add_to_model: bool = False, **kwargs, ): @@ -21,10 +26,10 @@ def direct_lake_schema_sync( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. add_to_model : bool, default=False @@ -45,10 +50,11 @@ def direct_lake_schema_sync( ) del kwargs["lakehouse_workspace"] - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) artifact_type, lakehouse_name, lakehouse_id, lakehouse_workspace_id = ( - get_direct_lake_source(dataset=dataset, workspace=workspace) + get_direct_lake_source(dataset=dataset_id, workspace=workspace_id) ) if artifact_type == "Warehouse": @@ -60,7 +66,7 @@ def direct_lake_schema_sync( lc = get_lakehouse_columns(lakehouse_name, lakehouse_workspace) with connect_semantic_model( - dataset=dataset, readonly=False, workspace=workspace + dataset=dataset_id, readonly=False, workspace=workspace_id ) as tom: for i, r in lc.iterrows(): @@ -86,7 +92,7 @@ def direct_lake_schema_sync( for c in tom.all_columns() ): print( - f"{icons.yellow_dot} The '{lakeCName}' column exists in the '{lakeTName}' lakehouse table but not in the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.yellow_dot} The '{lakeCName}' column exists in the '{lakeTName}' lakehouse table but not in the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) if add_to_model: dt = _convert_data_type(dType) @@ -97,5 +103,5 @@ def direct_lake_schema_sync( data_type=dt, ) print( - f"{icons.green_dot} The '{lakeCName}' column in the '{lakeTName}' lakehouse table was added to the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} The '{lakeCName}' column in the '{lakeTName}' lakehouse table was added to the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) diff --git a/src/sempy_labs/directlake/_dl_helper.py b/src/sempy_labs/directlake/_dl_helper.py index a4759a1..4d1252d 100644 --- a/src/sempy_labs/directlake/_dl_helper.py +++ b/src/sempy_labs/directlake/_dl_helper.py @@ -80,9 +80,9 @@ def check_fallback_reason( def generate_direct_lake_semantic_model( dataset: str, lakehouse_tables: Union[str, List[str]], - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, lakehouse: Optional[str] = None, - lakehouse_workspace: Optional[str] = None, + lakehouse_workspace: Optional[str | UUID] = None, schema: str = "dbo", overwrite: bool = False, refresh: bool = True, @@ -96,15 +96,15 @@ def generate_direct_lake_semantic_model( Name of the semantic model to be created. lakehouse_tables : str | List[str] The table(s) within the Fabric lakehouse to add to the semantic model. All columns from these tables will be added to the semantic model. - workspace : str, default=None - The Fabric workspace name in which the semantic model will reside. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model will reside. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. lakehouse : str, default=None The lakehouse which stores the delta tables which will feed the Direct Lake semantic model. Defaults to None which resolves to the attached lakehouse. - lakehouse_workspace : str, default=None - The Fabric workspace in which the lakehouse resides. + lakehouse_workspace : str | UUID, default=None + The Fabric workspace name or ID in which the lakehouse resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. schema : str, default="dbo" @@ -126,7 +126,7 @@ def generate_direct_lake_semantic_model( if isinstance(lakehouse_tables, str): lakehouse_tables = [lakehouse_tables] - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse_workspace is None: lakehouse_workspace = workspace if lakehouse is None: @@ -143,23 +143,23 @@ def generate_direct_lake_semantic_model( for t in lakehouse_tables: if t not in dfLT["Table Name"].values: raise ValueError( - f"{icons.red_dot} The '{t}' table does not exist as a delta table in the '{lakehouse}' within the '{workspace}' workspace." + f"{icons.red_dot} The '{t}' table does not exist as a delta table in the '{lakehouse}' within the '{workspace_name}' workspace." ) dfLC = get_lakehouse_columns(lakehouse=lakehouse, workspace=lakehouse_workspace) expr = generate_shared_expression( item_name=lakehouse, item_type="Lakehouse", workspace=lakehouse_workspace ) - dfD = fabric.list_datasets(workspace=workspace) + dfD = fabric.list_datasets(workspace=workspace_id) dfD_filt = dfD[dfD["Dataset Name"] == dataset] if len(dfD_filt) > 0 and not overwrite: raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace}' workspace already exists. Overwrite is set to False so the new semantic model has not been created." + f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace_name}' workspace already exists. Overwrite is set to False so the new semantic model has not been created." ) create_blank_semantic_model( - dataset=dataset, workspace=workspace, overwrite=overwrite + dataset=dataset, workspace=workspace_id, overwrite=overwrite ) @retry( @@ -168,7 +168,7 @@ def generate_direct_lake_semantic_model( ) def dyn_connect(): with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset, readonly=True, workspace=workspace_id ) as tom: tom.model @@ -177,7 +177,7 @@ def dyn_connect(): expression_name = "DatabaseQuery" with connect_semantic_model( - dataset=dataset, workspace=workspace, readonly=False + dataset=dataset, workspace=workspace_id, readonly=False ) as tom: if not any(e.Name == expression_name for e in tom.model.Expressions): tom.add_expression(name=expression_name, expression=expr) @@ -198,7 +198,7 @@ def dyn_connect(): ) if refresh: - refresh_semantic_model(dataset=dataset, workspace=workspace) + refresh_semantic_model(dataset=dataset, workspace=workspace_id) def get_direct_lake_source( diff --git a/src/sempy_labs/directlake/_generate_shared_expression.py b/src/sempy_labs/directlake/_generate_shared_expression.py index 4b3ccad..4ebe915 100644 --- a/src/sempy_labs/directlake/_generate_shared_expression.py +++ b/src/sempy_labs/directlake/_generate_shared_expression.py @@ -3,16 +3,18 @@ resolve_lakehouse_name, resolve_lakehouse_id, resolve_warehouse_id, + resolve_workspace_name_and_id, ) from typing import Optional import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def generate_shared_expression( item_name: Optional[str] = None, item_type: str = "Lakehouse", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> str: """ Dynamically generates the M expression used by a Direct Lake model for a given lakehouse/warehouse. @@ -24,8 +26,8 @@ def generate_shared_expression( Defaults to None which resolves to the lakehouse attached to the notebook. item_type : str, default="Lakehouse" The Fabric item name. Valid options: 'Lakehouse', 'Warehouse'. - workspace : str, default=None - The Fabric workspace used by the item. + workspace : str | UUID, default=None + The Fabric workspace name or ID used by the item. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -35,8 +37,7 @@ def generate_shared_expression( Shows the expression which can be used to connect a Direct Lake semantic model to its SQL Endpoint. """ - workspace = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_types = ["Lakehouse", "Warehouse"] item_type = item_type.capitalize() if item_type not in item_types: @@ -46,11 +47,11 @@ def generate_shared_expression( if item_name is None: item_id = fabric.get_lakehouse_id() - item_name = resolve_lakehouse_name(item_id, workspace) + item_name = resolve_lakehouse_name(item_id, workspace_id) elif item_name is not None and item_type == "Lakehouse": - item_id = resolve_lakehouse_id(lakehouse=item_name, workspace=workspace) + item_id = resolve_lakehouse_id(lakehouse=item_name, workspace=workspace_id) elif item_type == "Warehouse": - item_id = resolve_warehouse_id(warehouse=item_name, workspace=workspace) + item_id = resolve_warehouse_id(warehouse=item_name, workspace=workspace_id) client = fabric.FabricRestClient() item_type_rest = f"{item_type.lower()}s" @@ -72,7 +73,7 @@ def generate_shared_expression( if provStatus == "InProgress": raise ValueError( - f"{icons.red_dot} The SQL Endpoint for the '{item_name}' lakehouse within the '{workspace}' workspace has not yet been provisioned. Please wait until it has been provisioned." + f"{icons.red_dot} The SQL Endpoint for the '{item_name}' lakehouse within the '{workspace_name}' workspace has not yet been provisioned. Please wait until it has been provisioned." ) start_expr = "let\n\tdatabase = " diff --git a/src/sempy_labs/directlake/_get_directlake_lakehouse.py b/src/sempy_labs/directlake/_get_directlake_lakehouse.py index 9d474b3..f2883e3 100644 --- a/src/sempy_labs/directlake/_get_directlake_lakehouse.py +++ b/src/sempy_labs/directlake/_get_directlake_lakehouse.py @@ -3,46 +3,49 @@ resolve_lakehouse_id, resolve_lakehouse_name, get_direct_lake_sql_endpoint, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, ) from typing import Optional, Tuple from uuid import UUID def get_direct_lake_lakehouse( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, lakehouse: Optional[str] = None, - lakehouse_workspace: Optional[str] = None, + lakehouse_workspace: Optional[str | UUID] = None, ) -> Tuple[str, UUID]: """ Identifies the lakehouse used by a Direct Lake semantic model. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. lakehouse : str, default=None The Fabric lakehouse used by the Direct Lake semantic model. Defaults to None which resolves to the lakehouse attached to the notebook. - lakehouse_workspace : str, default=None - The Fabric workspace used by the lakehouse. + lakehouse_workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. Returns ------- - str, uuid.UUID + str, UUID The lakehouse name and lakehouse ID. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) if lakehouse_workspace is None: - lakehouse_workspace = workspace + lakehouse_workspace = workspace_name if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() @@ -56,7 +59,7 @@ def get_direct_lake_lakehouse( # f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace}' workspace is not in Direct Lake mode." # ) - sqlEndpointId = get_direct_lake_sql_endpoint(dataset, workspace) + sqlEndpointId = get_direct_lake_sql_endpoint(dataset_id, workspace_id) dfI = fabric.list_items(workspace=lakehouse_workspace, type="SQLEndpoint") dfI_filt = dfI[dfI["Id"] == sqlEndpointId] diff --git a/src/sempy_labs/directlake/_get_shared_expression.py b/src/sempy_labs/directlake/_get_shared_expression.py index 3a3e48e..3a56389 100644 --- a/src/sempy_labs/directlake/_get_shared_expression.py +++ b/src/sempy_labs/directlake/_get_shared_expression.py @@ -1,8 +1,9 @@ from typing import Optional +from uuid import UUID def get_shared_expression( - lakehouse: Optional[str] = None, workspace: Optional[str] = None + lakehouse: Optional[str] = None, workspace: Optional[str | UUID] = None ) -> str: """ Dynamically generates the M expression used by a Direct Lake model for a given lakehouse. @@ -12,8 +13,8 @@ def get_shared_expression( lakehouse : str, default=None The Fabric lakehouse used by the Direct Lake semantic model. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace used by the lakehouse. + workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. diff --git a/src/sempy_labs/directlake/_guardrails.py b/src/sempy_labs/directlake/_guardrails.py index 45016f0..83b0549 100644 --- a/src/sempy_labs/directlake/_guardrails.py +++ b/src/sempy_labs/directlake/_guardrails.py @@ -2,6 +2,10 @@ import pandas as pd from typing import Optional import sempy_labs._icons as icons +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_workspace_name_and_id, +) def get_direct_lake_guardrails() -> pd.DataFrame: @@ -28,14 +32,14 @@ def get_direct_lake_guardrails() -> pd.DataFrame: return df -def get_sku_size(workspace: Optional[str] = None) -> str: +def get_sku_size(workspace: Optional[str | UUID] = None) -> str: """ Shows the SKU size for a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -45,12 +49,12 @@ def get_sku_size(workspace: Optional[str] = None) -> str: The SKU size for a workspace. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfW = fabric.list_workspaces(filter=f"name eq '{workspace}'") + dfW = fabric.list_workspaces(filter=f"name eq '{workspace_name}'") if len(dfW) == 0: - raise ValueError(f"{icons.red_dot} The '{workspace}' is not a valid workspace.") + raise ValueError(f"{icons.red_dot} The '{workspace_name}' is not a valid workspace.") capacity_id = dfW["Capacity Id"].iloc[0] dfC = fabric.list_capacities() diff --git a/src/sempy_labs/directlake/_list_directlake_model_calc_tables.py b/src/sempy_labs/directlake/_list_directlake_model_calc_tables.py index 4bfffc2..2516d35 100644 --- a/src/sempy_labs/directlake/_list_directlake_model_calc_tables.py +++ b/src/sempy_labs/directlake/_list_directlake_model_calc_tables.py @@ -5,21 +5,26 @@ from typing import Optional from sempy._utils._log import log import sempy_labs._icons as icons +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) @log def list_direct_lake_model_calc_tables( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows the calculated tables and their respective DAX expression for a Direct Lake model (which has been migrated from import/DirectQuery). Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -29,12 +34,13 @@ def list_direct_lake_model_calc_tables( A pandas dataframe showing the calculated tables which were migrated to Direct Lake and whose DAX expressions are stored as model annotations. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) df = pd.DataFrame(columns=["Table Name", "Source Expression"]) with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: is_direct_lake = tom.is_direct_lake() @@ -44,8 +50,8 @@ def list_direct_lake_model_calc_tables( f"{icons.red_dot} The '{dataset}' semantic model is not in Direct Lake mode." ) else: - dfA = fabric.list_annotations(dataset=dataset, workspace=workspace) - dfT = list_tables(dataset, workspace) + dfA = fabric.list_annotations(dataset=dataset_id, workspace=workspace_id) + dfT = list_tables(dataset_id, workspace_id) dfA_filt = dfA[ (dfA["Object Type"] == "Model") & (dfA["Annotation Name"].isin(dfT["Name"])) diff --git a/src/sempy_labs/directlake/_show_unsupported_directlake_objects.py b/src/sempy_labs/directlake/_show_unsupported_directlake_objects.py index 4085682..6e03f92 100644 --- a/src/sempy_labs/directlake/_show_unsupported_directlake_objects.py +++ b/src/sempy_labs/directlake/_show_unsupported_directlake_objects.py @@ -1,13 +1,17 @@ import sempy.fabric as fabric import pandas as pd -from sempy_labs._helper_functions import format_dax_object_name +from sempy_labs._helper_functions import ( + format_dax_object_name, + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) from typing import Optional, Tuple from sempy._utils._log import log - +from uuid import UUID @log def show_unsupported_direct_lake_objects( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: """ Returns a list of a semantic model's objects which are not supported by Direct Lake based on @@ -15,10 +19,10 @@ def show_unsupported_direct_lake_objects( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -30,11 +34,12 @@ def show_unsupported_direct_lake_objects( pd.options.mode.chained_assignment = None - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfT = fabric.list_tables(dataset=dataset, workspace=workspace) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace) - dfR = fabric.list_relationships(dataset=dataset, workspace=workspace) + dfT = fabric.list_tables(dataset=dataset_id, workspace=workspace_id) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id) + dfR = fabric.list_relationships(dataset=dataset_id, workspace=workspace_id) # Calc tables dfT_filt = dfT[dfT["Type"] == "Calculated Table"] diff --git a/src/sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py b/src/sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py index bbd2778..3000deb 100644 --- a/src/sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py +++ b/src/sempy_labs/directlake/_update_directlake_model_lakehouse_connection.py @@ -2,42 +2,46 @@ from sempy_labs.directlake._generate_shared_expression import generate_shared_expression from sempy_labs._helper_functions import ( resolve_lakehouse_name, + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, ) from sempy_labs.tom import connect_semantic_model from typing import Optional import sempy_labs._icons as icons +from uuid import UUID def update_direct_lake_model_lakehouse_connection( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, lakehouse: Optional[str] = None, - lakehouse_workspace: Optional[str] = None, + lakehouse_workspace: Optional[str | UUID] = None, ): """ Remaps a Direct Lake semantic model's SQL Endpoint connection to a new lakehouse. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name in which the semantic model exists. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model exists. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. lakehouse : str, default=None The Fabric lakehouse used by the Direct Lake semantic model. Defaults to None which resolves to the lakehouse attached to the notebook. - lakehouse_workspace : str, default=None - The Fabric workspace used by the lakehouse. + lakehouse_workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) if lakehouse_workspace is None: - lakehouse_workspace = workspace + lakehouse_workspace = workspace_name if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() @@ -50,7 +54,7 @@ def update_direct_lake_model_lakehouse_connection( if len(dfI_filt) == 0: raise ValueError( f"{icons.red_dot} The '{lakehouse}' lakehouse does not exist within the '{lakehouse_workspace}' workspace. " - f"Therefore it cannot be used to support the '{dataset}' semantic model within the '{workspace}' workspace." + f"Therefore it cannot be used to support the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) icons.sll_tags.append("UpdateDLConnection") @@ -60,37 +64,37 @@ def update_direct_lake_model_lakehouse_connection( ) with connect_semantic_model( - dataset=dataset, readonly=False, workspace=workspace + dataset=dataset_id, readonly=False, workspace=workspace_id ) as tom: if not tom.is_direct_lake(): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model is not in Direct Lake. This function is only applicable to Direct Lake semantic models." + f"{icons.red_dot} The '{dataset_name}' semantic model is not in Direct Lake. This function is only applicable to Direct Lake semantic models." ) tom.model.Expressions["DatabaseQuery"].Expression = shEx print( - f"{icons.green_dot} The expression in the '{dataset}' semantic model has been updated to point to the '{lakehouse}' lakehouse in the '{lakehouse_workspace}' workspace." + f"{icons.green_dot} The expression in the '{dataset_name}' semantic model has been updated to point to the '{lakehouse}' lakehouse in the '{lakehouse_workspace}' workspace." ) def update_direct_lake_model_connection( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, source: Optional[str] = None, source_type: Optional[str] = "Lakehouse", - source_workspace: Optional[str] = None, + source_workspace: Optional[str | UUID] = None, ): """ Remaps a Direct Lake semantic model's SQL Endpoint connection to a new lakehouse/warehouse. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name in which the semantic model exists. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model exists. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. source : str, default=None @@ -98,14 +102,14 @@ def update_direct_lake_model_connection( Defaults to None which resolves to the lakehouse attached to the notebook. source_type : str, default="Lakehouse" The type of source for the Direct Lake semantic model. Valid options: "Lakehouse", "Warehouse". - source_workspace : str, default=None - The Fabric workspace used by the lakehouse/warehouse. + source_workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse/warehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) source_type = source_type.capitalize() @@ -115,7 +119,7 @@ def update_direct_lake_model_connection( ) if source_workspace is None: - source_workspace = workspace + source_workspace = workspace_name if source is None: source_id = fabric.get_lakehouse_id() @@ -135,16 +139,16 @@ def update_direct_lake_model_connection( ) with connect_semantic_model( - dataset=dataset, readonly=False, workspace=workspace + dataset=dataset_id, readonly=False, workspace=workspace_id ) as tom: if not tom.is_direct_lake(): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model is not in Direct Lake. This function is only applicable to Direct Lake semantic models." + f"{icons.red_dot} The '{dataset_name}' semantic model within the '{workspace_name}' workspace is not in Direct Lake. This function is only applicable to Direct Lake semantic models." ) tom.model.Expressions["DatabaseQuery"].Expression = shEx print( - f"{icons.green_dot} The expression in the '{dataset}' semantic model has been updated to point to the '{source}' {source_type.lower()} in the '{source_workspace}' workspace." + f"{icons.green_dot} The expression in the '{dataset_name}' semantic model within the '{workspace_name}' workspace has been updated to point to the '{source}' {source_type.lower()} in the '{source_workspace}' workspace." ) diff --git a/src/sempy_labs/directlake/_update_directlake_partition_entity.py b/src/sempy_labs/directlake/_update_directlake_partition_entity.py index 6822a69..2803718 100644 --- a/src/sempy_labs/directlake/_update_directlake_partition_entity.py +++ b/src/sempy_labs/directlake/_update_directlake_partition_entity.py @@ -3,36 +3,41 @@ from sempy_labs.tom import connect_semantic_model from sempy_labs._refresh_semantic_model import refresh_semantic_model from sempy_labs.directlake._dl_helper import get_direct_lake_source -from sempy_labs._helper_functions import _convert_data_type +from sempy_labs._helper_functions import ( + _convert_data_type, + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) from typing import List, Optional, Union import sempy_labs._icons as icons +from uuid import UUID def update_direct_lake_partition_entity( - dataset: str, + dataset: str | UUID, table_name: Union[str, List[str]], entity_name: Union[str, List[str]], - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Remaps a table (or tables) in a Direct Lake semantic model to a table in a lakehouse. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. table_name : str, List[str] Name of the table(s) in the semantic model. entity_name : str, List[str] Name of the lakehouse table to be mapped to the semantic model table. - workspace : str, default=None - The Fabric workspace name in which the semantic model exists. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model exists. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) # Support both str & list types if isinstance(table_name, str): @@ -48,12 +53,12 @@ def update_direct_lake_partition_entity( icons.sll_tags.append("UpdateDLPartition") with connect_semantic_model( - dataset=dataset, readonly=False, workspace=workspace + dataset=dataset_id, readonly=False, workspace=workspace_id ) as tom: if not tom.is_direct_lake(): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model within the '{workspace}' workspace is not in Direct Lake mode." + f"{icons.red_dot} The '{dataset_name}' semantic model within the '{workspace_name}' workspace is not in Direct Lake mode." ) for tName in table_name: @@ -68,37 +73,37 @@ def update_direct_lake_partition_entity( if part_name is None: raise ValueError( - f"{icons.red_dot} The '{tName}' table in the '{dataset}' semantic model has not been updated." + f"{icons.red_dot} The '{tName}' table in the '{dataset_name}' semantic model has not been updated." ) tom.model.Tables[tName].Partitions[part_name].Source.EntityName = eName print( - f"{icons.green_dot} The '{tName}' table in the '{dataset}' semantic model has been updated to point to the '{eName}' table." + f"{icons.green_dot} The '{tName}' table in the '{dataset_name}' semantic model within the '{workspace_name}' workspace has been updated to point to the '{eName}' table." ) def add_table_to_direct_lake_semantic_model( - dataset: str, + dataset: str | UUID, table_name: str, lakehouse_table_name: str, refresh: bool = True, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Adds a table and all of its columns to a Direct Lake semantic model, based on a Fabric lakehouse table. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. table_name : str, List[str] Name of the table in the semantic model. lakehouse_table_name : str The name of the Fabric lakehouse table. refresh : bool, default=True Refreshes the table after it is added to the semantic model. - workspace : str, default=None - The name of the Fabric workspace in which the semantic model resides. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which the semantic model resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -111,10 +116,11 @@ def add_table_to_direct_lake_semantic_model( from sempy_labs.lakehouse._get_lakehouse_columns import get_lakehouse_columns from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) artifact_type, lakehouse_name, lakehouse_id, lakehouse_workspace_id = ( - get_direct_lake_source(dataset=dataset, workspace=workspace) + get_direct_lake_source(dataset=dataset_id, workspace=workspace_id) ) if artifact_type == "Warehouse": @@ -125,7 +131,7 @@ def add_table_to_direct_lake_semantic_model( lakehouse_workspace = fabric.resolve_workspace_name(lakehouse_workspace_id) with connect_semantic_model( - dataset=dataset, readonly=False, workspace=workspace + dataset=dataset_id, readonly=False, workspace=workspace_id ) as tom: table_count = tom.model.Tables.Count @@ -148,12 +154,12 @@ def add_table_to_direct_lake_semantic_model( == TOM.PartitionSourceType.Entity ) raise ValueError( - f"The '{lakehouse_table_name}' table already exists in the '{dataset}' semantic model within the '{workspace}' workspace as the '{t_name}' table." + f"The '{lakehouse_table_name}' table already exists in the '{dataset_name}' semantic model within the '{workspace_name}' workspace as the '{t_name}' table." ) if any(t.Name == table_name for t in tom.model.Tables): raise ValueError( - f"The '{table_name}' table already exists in the '{dataset}' semantic model within the '{workspace}' workspace." + f"The '{table_name}' table already exists in the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) dfL = get_lakehouse_tables( @@ -173,13 +179,13 @@ def add_table_to_direct_lake_semantic_model( tom.add_table(name=table_name) print( - f"{icons.green_dot} The '{table_name}' table has been added to the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} The '{table_name}' table has been added to the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) tom.add_entity_partition( table_name=table_name, entity_name=lakehouse_table_name ) print( - f"{icons.green_dot} The '{lakehouse_table_name}' partition has been added to the '{table_name}' table in the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} The '{lakehouse_table_name}' partition has been added to the '{table_name}' table in the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) for i, r in dfLC_filt.iterrows(): @@ -193,10 +199,10 @@ def add_table_to_direct_lake_semantic_model( data_type=dt, ) print( - f"{icons.green_dot} The '{lakeCName}' column has been added to the '{table_name}' table as a '{dt}' data type in the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} The '{lakeCName}' column has been added to the '{table_name}' table as a '{dt}' data type in the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) if refresh: refresh_semantic_model( - dataset=dataset, tables=table_name, workspace=workspace + dataset=dataset_id, tables=table_name, workspace=workspace_id ) diff --git a/src/sempy_labs/directlake/_warm_cache.py b/src/sempy_labs/directlake/_warm_cache.py index 17bc1ed..753425b 100644 --- a/src/sempy_labs/directlake/_warm_cache.py +++ b/src/sempy_labs/directlake/_warm_cache.py @@ -3,34 +3,39 @@ from tqdm.auto import tqdm import numpy as np import time -from sempy_labs._helper_functions import format_dax_object_name +from sempy_labs._helper_functions import ( + format_dax_object_name, + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) from sempy_labs._refresh_semantic_model import refresh_semantic_model from sempy_labs._model_dependencies import get_measure_dependencies from typing import Optional from sempy._utils._log import log import sempy_labs._icons as icons +from uuid import UUID @log def warm_direct_lake_cache_perspective( - dataset: str, + dataset: str | UUID, perspective: str, add_dependencies: bool = False, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> pd.DataFrame: """ Warms the cache of a Direct Lake semantic model by running a simple DAX query against the columns in a perspective. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. perspective : str Name of the perspective which contains objects to be used for warming the cache. add_dependencies : bool, default=False Includes object dependencies in the cache warming process. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -40,15 +45,16 @@ def warm_direct_lake_cache_perspective( Returns a pandas dataframe showing the columns that have been put into memory. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfP = fabric.list_partitions(dataset=dataset, workspace=workspace) - if not any(r["Mode"] == "DirectLake" for i, r in dfP.iterrows()): + dfP = fabric.list_partitions(dataset=dataset_id, workspace=workspace_id) + if not any(r["Mode"] == "DirectLake" for _, r in dfP.iterrows()): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model in the '{workspace}' workspace is not in Direct Lake mode. This function is specifically for semantic models in Direct Lake mode." + f"{icons.red_dot} The '{dataset_name}' semantic model in the '{workspace_name}' workspace is not in Direct Lake mode. This function is specifically for semantic models in Direct Lake mode." ) - dfPersp = fabric.list_perspectives(dataset=dataset, workspace=workspace) + dfPersp = fabric.list_perspectives(dataset=dataset_id, workspace=workspace_id) dfPersp["DAX Object Name"] = format_dax_object_name( dfPersp["Table Name"], dfPersp["Object Name"] ) @@ -65,7 +71,7 @@ def warm_direct_lake_cache_perspective( if add_dependencies: # Measure dependencies - md = get_measure_dependencies(dataset, workspace) + md = get_measure_dependencies(dataset_id, workspace_id) md["Referenced Full Object"] = format_dax_object_name( md["Referenced Table"], md["Referenced Object"] ) @@ -78,7 +84,7 @@ def warm_direct_lake_cache_perspective( # Hierarchy dependencies dfPersp_h = dfPersp_filt[(dfPersp_filt["Object Type"] == "Hierarchy")] - dfH = fabric.list_hierarchies(dataset=dataset, workspace=workspace) + dfH = fabric.list_hierarchies(dataset=dataset_id, workspace=workspace_id) dfH["Hierarchy Object"] = format_dax_object_name( dfH["Table Name"], dfH["Hierarchy Name"] ) @@ -92,7 +98,7 @@ def warm_direct_lake_cache_perspective( # Relationship dependencies unique_table_names = dfPersp_filt["Table Name"].unique() - dfR = fabric.list_relationships(dataset=dataset, workspace=workspace) + dfR = fabric.list_relationships(dataset=dataset_id, workspace=workspace_id) dfR["From Object"] = format_dax_object_name( dfR["From Table"], dfR["From Column"] ) @@ -129,7 +135,7 @@ def warm_direct_lake_cache_perspective( bar.set_description(f"Warming the '{tableName}' table...") css = ",".join(map(str, filtered_list)) dax = """EVALUATE TOPN(1,SUMMARIZECOLUMNS(""" + css + "))" "" - fabric.evaluate_dax(dataset=dataset, dax_string=dax, workspace=workspace) + fabric.evaluate_dax(dataset=dataset_id, dax_string=dax, workspace=workspace_id) print(f"{icons.green_dot} The following columns have been put into memory:") @@ -144,17 +150,17 @@ def warm_direct_lake_cache_perspective( @log def warm_direct_lake_cache_isresident( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Performs a refresh on the semantic model and puts the columns which were in memory prior to the refresh back into memory. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -164,16 +170,17 @@ def warm_direct_lake_cache_isresident( Returns a pandas dataframe showing the columns that have been put into memory. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfP = fabric.list_partitions(dataset=dataset, workspace=workspace) - if not any(r["Mode"] == "DirectLake" for i, r in dfP.iterrows()): + dfP = fabric.list_partitions(dataset=dataset_id, workspace=workspace_id) + if not any(r["Mode"] == "DirectLake" for _, r in dfP.iterrows()): raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model in the '{workspace}' workspace is not in Direct Lake mode. This function is specifically for semantic models in Direct Lake mode." + f"{icons.red_dot} The '{dataset_name}' semantic model in the '{workspace_name}' workspace is not in Direct Lake mode. This function is specifically for semantic models in Direct Lake mode." ) # Identify columns which are currently in memory (Is Resident = True) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace, extended=True) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id, extended=True) dfC["DAX Object Name"] = format_dax_object_name( dfC["Table Name"], dfC["Column Name"] ) @@ -181,11 +188,11 @@ def warm_direct_lake_cache_isresident( if len(dfC_filtered) == 0: raise ValueError( - f"{icons.yellow_dot} At present, no columns are in memory in the '{dataset}' semantic model in the '{workspace}' workspace." + f"{icons.yellow_dot} At present, no columns are in memory in the '{dataset_name}' semantic model in the '{workspace_name}' workspace." ) # Refresh/frame dataset - refresh_semantic_model(dataset=dataset, refresh_type="full", workspace=workspace) + refresh_semantic_model(dataset=dataset_id, refresh_type="full", workspace=workspace_id) time.sleep(2) # Run basic query to get columns into memory; completed one table at a time (so as not to overload the capacity) @@ -198,7 +205,7 @@ def warm_direct_lake_cache_isresident( .tolist() ) dax = f"""EVALUATE TOPN(1,SUMMARIZECOLUMNS({css}))""" - fabric.evaluate_dax(dataset=dataset, dax_string=dax, workspace=workspace) + fabric.evaluate_dax(dataset=dataset_id, dax_string=dax, workspace=workspace_id) print( f"{icons.green_dot} The following columns have been put into memory. Temperature indicates the column temperature prior to the semantic model refresh." diff --git a/src/sempy_labs/lakehouse/_get_lakehouse_columns.py b/src/sempy_labs/lakehouse/_get_lakehouse_columns.py index c8b3838..729eb57 100644 --- a/src/sempy_labs/lakehouse/_get_lakehouse_columns.py +++ b/src/sempy_labs/lakehouse/_get_lakehouse_columns.py @@ -5,14 +5,16 @@ resolve_lakehouse_name, format_dax_object_name, resolve_lakehouse_id, + resolve_workspace_name_and_id, ) from typing import Optional from sempy._utils._log import log +from uuid import UUID @log def get_lakehouse_columns( - lakehouse: Optional[str] = None, workspace: Optional[str] = None + lakehouse: Optional[str] = None, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows the tables and columns of a lakehouse and their respective properties. @@ -22,8 +24,8 @@ def get_lakehouse_columns( lakehouse : str, default=None The Fabric lakehouse. Defaults to None which resolves to the lakehouse attached to the notebook. - lakehouse_workspace : str, default=None - The Fabric workspace used by the lakehouse. + lakehouse_workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -46,18 +48,18 @@ def get_lakehouse_columns( ] ) - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() - lakehouse = resolve_lakehouse_name(lakehouse_id, workspace) + lakehouse = resolve_lakehouse_name(lakehouse_id, workspace_id) else: - lakehouse_id = resolve_lakehouse_id(lakehouse, workspace) + lakehouse_id = resolve_lakehouse_id(lakehouse, workspace_id) spark = SparkSession.builder.getOrCreate() tables = get_lakehouse_tables( - lakehouse=lakehouse, workspace=workspace, extended=False, count_rows=False + lakehouse=lakehouse, workspace=workspace_id, extended=False, count_rows=False ) tables_filt = tables[tables["Format"] == "delta"] @@ -70,7 +72,7 @@ def get_lakehouse_columns( for cName, data_type in sparkdf.dtypes: tc = format_dax_object_name(tName, cName) new_data = { - "Workspace Name": workspace, + "Workspace Name": workspace_name, "Lakehouse Name": lakehouse, "Table Name": tName, "Column Name": cName, diff --git a/src/sempy_labs/lakehouse/_get_lakehouse_tables.py b/src/sempy_labs/lakehouse/_get_lakehouse_tables.py index 9b4a469..3c689c5 100644 --- a/src/sempy_labs/lakehouse/_get_lakehouse_tables.py +++ b/src/sempy_labs/lakehouse/_get_lakehouse_tables.py @@ -18,12 +18,13 @@ import sempy_labs._icons as icons from sempy._utils._log import log from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID @log def get_lakehouse_tables( lakehouse: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, extended: bool = False, count_rows: bool = False, export: bool = False, @@ -38,8 +39,8 @@ def get_lakehouse_tables( lakehouse : str, default=None The Fabric lakehouse. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace used by the lakehouse. + workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. extended : bool, default=False @@ -66,13 +67,13 @@ def get_lakehouse_tables( ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() - lakehouse = resolve_lakehouse_name(lakehouse_id, workspace) + lakehouse = resolve_lakehouse_name(lakehouse_id, workspace_id) else: - lakehouse_id = resolve_lakehouse_id(lakehouse, workspace) + lakehouse_id = resolve_lakehouse_id(lakehouse, workspace_id) if count_rows: # Setting countrows defaults to extended=True extended = True @@ -104,7 +105,7 @@ def get_lakehouse_tables( for r in responses: for i in r.get("data", []): new_data = { - "Workspace Name": workspace, + "Workspace Name": workspace_name, "Lakehouse Name": lakehouse, "Table Name": i.get("name"), "Format": i.get("format"), @@ -117,7 +118,7 @@ def get_lakehouse_tables( df = pd.concat(dfs, ignore_index=True) if extended: - sku_value = get_sku_size(workspace) + sku_value = get_sku_size(workspace_id) guardrail = get_directlake_guardrails_for_sku(sku_value) spark = SparkSession.builder.getOrCreate() df["Files"] = None @@ -182,7 +183,7 @@ def get_lakehouse_tables( lakehouse_id = fabric.get_lakehouse_id() lakehouse = resolve_lakehouse_name( - lakehouse_id=lakehouse_id, workspace=workspace + lakehouse_id=lakehouse_id, workspace=workspace_id ) lakeTName = "lakehouse_table_details" lakeT_filt = df[df["Table Name"] == lakeTName] diff --git a/src/sempy_labs/lakehouse/_lakehouse.py b/src/sempy_labs/lakehouse/_lakehouse.py index 26f1b97..f2b9d34 100644 --- a/src/sempy_labs/lakehouse/_lakehouse.py +++ b/src/sempy_labs/lakehouse/_lakehouse.py @@ -1,8 +1,12 @@ import sempy.fabric as fabric from tqdm.auto import tqdm -from sempy_labs._helper_functions import resolve_lakehouse_name +from sempy_labs._helper_functions import ( + resolve_lakehouse_name, + resolve_workspace_name_and_id, +) from typing import List, Optional, Union from sempy._utils._log import log +from uuid import UUID def lakehouse_attached() -> bool: @@ -29,7 +33,7 @@ def lakehouse_attached() -> bool: def optimize_lakehouse_tables( tables: Optional[Union[str, List[str]]] = None, lakehouse: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Runs the `OPTIMIZE `_ function over the specified lakehouse tables. @@ -42,8 +46,8 @@ def optimize_lakehouse_tables( lakehouse : str, default=None The Fabric lakehouse. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace used by the lakehouse. + workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -52,13 +56,13 @@ def optimize_lakehouse_tables( from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables from delta import DeltaTable - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() - lakehouse = resolve_lakehouse_name(lakehouse_id, workspace) + lakehouse = resolve_lakehouse_name(lakehouse_id, workspace_id) - lakeTables = get_lakehouse_tables(lakehouse=lakehouse, workspace=workspace) + lakeTables = get_lakehouse_tables(lakehouse=lakehouse, workspace=workspace_id) lakeTablesDelta = lakeTables[lakeTables["Format"] == "delta"] if isinstance(tables, str): @@ -83,7 +87,7 @@ def optimize_lakehouse_tables( def vacuum_lakehouse_tables( tables: Optional[Union[str, List[str]]] = None, lakehouse: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, retain_n_hours: Optional[int] = None, ): """ @@ -96,8 +100,8 @@ def vacuum_lakehouse_tables( lakehouse : str, default=None The Fabric lakehouse. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace used by the lakehouse. + workspace : str | UUID, default=None + The Fabric workspace name or ID used by the lakehouse. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. retain_n_hours : int, default=None @@ -111,13 +115,13 @@ def vacuum_lakehouse_tables( from sempy_labs.lakehouse._get_lakehouse_tables import get_lakehouse_tables from delta import DeltaTable - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() - lakehouse = resolve_lakehouse_name(lakehouse_id, workspace) + lakehouse = resolve_lakehouse_name(lakehouse_id, workspace_id) - lakeTables = get_lakehouse_tables(lakehouse=lakehouse, workspace=workspace) + lakeTables = get_lakehouse_tables(lakehouse=lakehouse, workspace=workspace_id) lakeTablesDelta = lakeTables[lakeTables["Format"] == "delta"] if isinstance(tables, str): diff --git a/src/sempy_labs/lakehouse/_shortcuts.py b/src/sempy_labs/lakehouse/_shortcuts.py index e287146..5789ed2 100644 --- a/src/sempy_labs/lakehouse/_shortcuts.py +++ b/src/sempy_labs/lakehouse/_shortcuts.py @@ -7,14 +7,15 @@ from typing import Optional import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def create_shortcut_onelake( table_name: str, source_lakehouse: str, - source_workspace: str, + source_workspace: str | UUID, destination_lakehouse: str, - destination_workspace: Optional[str] = None, + destination_workspace: Optional[str | UUID] = None, shortcut_name: Optional[str] = None, ): """ @@ -28,28 +29,34 @@ def create_shortcut_onelake( The table name for which a shortcut will be created. source_lakehouse : str The Fabric lakehouse in which the table resides. - source_workspace : str - The name of the Fabric workspace in which the source lakehouse exists. + source_workspace : str | UUID + The name or ID of the Fabric workspace in which the source lakehouse exists. destination_lakehouse : str The Fabric lakehouse in which the shortcut will be created. - destination_workspace : str, default=None - The name of the Fabric workspace in which the shortcut will be created. + destination_workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which the shortcut will be created. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. shortcut_name : str, default=None The name of the shortcut 'table' to be created. This defaults to the 'table_name' parameter value. """ - sourceWorkspaceId = fabric.resolve_workspace_id(source_workspace) - sourceLakehouseId = resolve_lakehouse_id(source_lakehouse, source_workspace) + (source_workspace_name, source_workspace_id) = resolve_workspace_name_and_id(source_workspace) + source_lakehouse_id = resolve_lakehouse_id(source_lakehouse, source_workspace_id) + source_lakehouse_name = fabric.resolve_item_name(item_id=source_lakehouse_id, type='Lakehouse', workspace=destination_workspace_id) if destination_workspace is None: - destination_workspace = source_workspace + destination_workspace_name = source_workspace_name + destination_workspace_id = source_workspace_id + else: + destination_workspace_name = destination_workspace + destination_workspace_id = fabric.resolve_workspace_id(destination_workspace_name) - destinationWorkspaceId = fabric.resolve_workspace_id(destination_workspace) - destinationLakehouseId = resolve_lakehouse_id( + destination_workspace_id = fabric.resolve_workspace_id(destination_workspace) + destination_lakehouse_id = resolve_lakehouse_id( destination_lakehouse, destination_workspace ) + destination_lakehouse_name = fabric.resolve_item_name(item_id=destination_lakehouse_id, type='Lakehouse', workspace=destination_workspace_id) if shortcut_name is None: shortcut_name = table_name @@ -62,8 +69,8 @@ def create_shortcut_onelake( "name": shortcut_name.replace(" ", ""), "target": { "oneLake": { - "workspaceId": sourceWorkspaceId, - "itemId": sourceLakehouseId, + "workspaceId": source_workspace_id, + "itemId": source_lakehouse_id, "path": tablePath, } }, @@ -71,13 +78,13 @@ def create_shortcut_onelake( try: response = client.post( - f"/v1/workspaces/{destinationWorkspaceId}/items/{destinationLakehouseId}/shortcuts", + f"/v1/workspaces/{destination_workspace_id}/items/{destination_lakehouse_id}/shortcuts", json=request_body, ) if response.status_code == 201: print( - f"{icons.green_dot} The shortcut '{shortcut_name}' was created in the '{destination_lakehouse}' lakehouse within" - f" the '{destination_workspace} workspace. It is based on the '{table_name}' table in the '{source_lakehouse}' lakehouse within the '{source_workspace}' workspace." + f"{icons.green_dot} The shortcut '{shortcut_name}' was created in the '{destination_lakehouse_name}' lakehouse within" + f" the '{destination_workspace_name} workspace. It is based on the '{table_name}' table in the '{source_lakehouse_name}' lakehouse within the '{source_workspace_name}' workspace." ) else: print(response.status_code) @@ -166,7 +173,7 @@ def create_shortcut( def delete_shortcut( - shortcut_name: str, lakehouse: Optional[str] = None, workspace: Optional[str] = None + shortcut_name: str, lakehouse: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Deletes a shortcut. @@ -180,19 +187,19 @@ def delete_shortcut( lakehouse : str, default=None The Fabric lakehouse name in which the shortcut resides. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The name of the Fabric workspace in which lakehouse resides. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which lakehouse resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() - lakehouse = resolve_lakehouse_name(lakehouse_id, workspace) + lakehouse = resolve_lakehouse_name(lakehouse_id, workspace_id) else: - lakehouse_id = resolve_lakehouse_id(lakehouse, workspace) + lakehouse_id = resolve_lakehouse_id(lakehouse, workspace_id) client = fabric.FabricRestClient() response = client.delete( @@ -202,5 +209,5 @@ def delete_shortcut( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{shortcut_name}' shortcut in the '{lakehouse}' within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{shortcut_name}' shortcut in the '{lakehouse}' within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/migration/_create_pqt_file.py b/src/sempy_labs/migration/_create_pqt_file.py index e3edd3f..637a1a8 100644 --- a/src/sempy_labs/migration/_create_pqt_file.py +++ b/src/sempy_labs/migration/_create_pqt_file.py @@ -1,5 +1,4 @@ import sempy -import sempy.fabric as fabric import json import os import shutil @@ -7,12 +6,16 @@ from sempy._utils._log import log from typing import Optional import sempy_labs._icons as icons - +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) @log def create_pqt_file( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, file_name: str = "PowerQueryTemplate", ): """ @@ -24,10 +27,10 @@ def create_pqt_file( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. file_name : str, default='PowerQueryTemplate' @@ -43,19 +46,20 @@ def create_pqt_file( f"{icons.red_dot} In order to run the 'create_pqt_file' function, a lakehouse must be attached to the notebook. Please attach a lakehouse to this notebook." ) - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) folderPath = "/lakehouse/default/Files" subFolderPath = os.path.join(folderPath, "pqtnewfolder") with connect_semantic_model( - dataset=dataset, workspace=workspace, readonly=True + dataset=dataset_id, workspace=workspace_id, readonly=True ) as tom: if not any( p.SourceType == TOM.PartitionSourceType.M for p in tom.all_partitions() ) and not any(t.RefreshPolicy for t in tom.model.Tables): print( - f"{icons.info} The '{dataset}' semantic model within the '{workspace}' workspace has no Power Query logic." + f"{icons.info} The '{dataset_name}' semantic model within the '{workspace_name}' workspace has no Power Query logic." ) return @@ -220,7 +224,7 @@ def obj_to_dict(obj): shutil.rmtree(subFolderPath, ignore_errors=True) print( - f"{icons.green_dot} '{file_name}.pqt' has been created based on the '{dataset}' semantic model in the '{workspace}' workspace within the Files section of your lakehouse." + f"{icons.green_dot} '{file_name}.pqt' has been created based on the '{dataset_name}' semantic model in the '{workspace_name}' workspace within the Files section of your lakehouse." ) a = 0 diff --git a/src/sempy_labs/migration/_migrate_calctables_to_lakehouse.py b/src/sempy_labs/migration/_migrate_calctables_to_lakehouse.py index b4aecea..8439c0f 100644 --- a/src/sempy_labs/migration/_migrate_calctables_to_lakehouse.py +++ b/src/sempy_labs/migration/_migrate_calctables_to_lakehouse.py @@ -16,7 +16,6 @@ from sempy._utils._log import log import sempy_labs._icons as icons - @log def migrate_calc_tables_to_lakehouse( dataset: str, diff --git a/src/sempy_labs/migration/_refresh_calc_tables.py b/src/sempy_labs/migration/_refresh_calc_tables.py index 7071a3b..b921a3f 100644 --- a/src/sempy_labs/migration/_refresh_calc_tables.py +++ b/src/sempy_labs/migration/_refresh_calc_tables.py @@ -7,25 +7,31 @@ from typing import Optional from sempy._utils._log import log import sempy_labs._icons as icons +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, +) @log -def refresh_calc_tables(dataset: str, workspace: Optional[str] = None): +def refresh_calc_tables(dataset: str | UUID, workspace: Optional[str | UUID] = None): """ Recreates the delta tables in the lakehouse based on the DAX expressions stored as model annotations in the Direct Lake semantic model. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ spark = SparkSession.builder.getOrCreate() - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) icons.sll_tags.append("DirectLakeMigration") @retry( @@ -34,7 +40,7 @@ def refresh_calc_tables(dataset: str, workspace: Optional[str] = None): ) def dyn_connect(): with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: tom.model @@ -42,7 +48,7 @@ def dyn_connect(): dyn_connect() with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: for a in tom.model.Annotations: if any(a.Name == t.Name for t in tom.model.Tables): @@ -56,9 +62,9 @@ def dyn_connect(): try: df = fabric.evaluate_dax( - dataset=dataset, + dataset=dataset_id, dax_string=daxquery, - workspace=workspace, + workspace=workspace_id, ) # Update column names for non-field parameters diff --git a/src/sempy_labs/report/_download_report.py b/src/sempy_labs/report/_download_report.py index 6501bed..5cb6f0e 100644 --- a/src/sempy_labs/report/_download_report.py +++ b/src/sempy_labs/report/_download_report.py @@ -7,13 +7,14 @@ ) from sempy_labs.lakehouse._lakehouse import lakehouse_attached from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def download_report( report: str, file_name: Optional[str] = None, download_type: str = "LiveConnect", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Downloads the specified report from the specified workspace to a Power BI .pbix file. @@ -29,8 +30,8 @@ def download_report( Defaults to None which resolves to the name of the report. download_type : str, default="LiveConnect" The type of download. Valid values are "LiveConnect" and "IncludeModel". - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -40,10 +41,11 @@ def download_report( f"{icons.red_dot} A lakehouse must be attached to the notebook." ) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) lakehouse_id = fabric.get_lakehouse_id() - workspace_name = fabric.resolve_workspace_name() + lakehouse_workspace = fabric.resolve_workspace_name() lakehouse_name = resolve_lakehouse_name( - lakehouse_id=lakehouse_id, workspace=workspace_name + lakehouse_id=lakehouse_id, workspace=lakehouse_workspace ) download_types = ["LiveConnect", "IncludeModel"] @@ -53,9 +55,8 @@ def download_report( ) file_name = file_name or report - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) report_id = fabric.resolve_item_id( - item_name=report, type="Report", workspace=workspace + item_name=report, type="Report", workspace=workspace_id ) client = fabric.PowerBIRestClient() @@ -71,5 +72,5 @@ def download_report( file.write(response.content) print( - f"{icons.green_dot} The '{report}' report within the '{workspace}' workspace has been exported as the '{file_name}' file in the '{lakehouse_name}' lakehouse within the '{workspace_name} workspace." + f"{icons.green_dot} The '{report}' report within the '{workspace_name}' workspace has been exported as the '{file_name}' file in the '{lakehouse_name}' lakehouse within the '{lakehouse_workspace}' workspace." ) diff --git a/src/sempy_labs/report/_generate_report.py b/src/sempy_labs/report/_generate_report.py index a185276..82ae95b 100644 --- a/src/sempy_labs/report/_generate_report.py +++ b/src/sempy_labs/report/_generate_report.py @@ -7,18 +7,20 @@ resolve_workspace_name_and_id, _conv_b64, resolve_report_id, + resolve_dataset_name_and_id, lro, ) import sempy_labs._icons as icons from sempy._utils._log import log +from uuid import UUID def create_report_from_reportjson( report: str, - dataset: str, + dataset: str | UUID, report_json: dict, theme_json: Optional[dict] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates a report based on a report.json file (and an optional themes.json file). @@ -29,36 +31,27 @@ def create_report_from_reportjson( ---------- report : str Name of the report. - dataset : str - Name of the semantic model to connect to the report. + dataset : str | UUID + Name or ID of the semantic model to connect to the report. report_json : dict The report.json file to be used to create the report. theme_json : dict, default=None The theme.json file to be used for the theme of the report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - - dfI = fabric.list_items(workspace=workspace) - - dfI_model = dfI[(dfI["Display Name"] == dataset) & (dfI["Type"] == "SemanticModel")] - - if len(dfI_model) == 0: - raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model does not exist in the '{workspace}' workspace." - ) - - datasetId = dfI_model["Id"].iloc[0] + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfI_rpt = dfI[(dfI["Display Name"] == report) & (dfI["Type"] == "Report")] + dfI = fabric.list_items(workspace=workspace, type='Report') + dfI_rpt = dfI[dfI["Display Name"] == report] - if len(dfI_rpt) > 0: + if not dfI_rpt.empty: print( - f"{icons.yellow_dot} '{report}' already exists as a report in the '{workspace}' workspace." + f"{icons.yellow_dot} '{report}' report already exists in the '{workspace_name}' workspace." ) return @@ -71,7 +64,7 @@ def create_report_from_reportjson( "connectionString": None, "pbiServiceModelId": None, "pbiModelVirtualServerName": "sobe_wowvirtualserver", - "pbiModelDatabaseName": datasetId, + "pbiModelDatabaseName": dataset_id, "name": "EntityDataSource", "connectionType": "pbiServiceXmlaStyleLive", }, @@ -116,12 +109,12 @@ def create_report_from_reportjson( lro(client, response, status_codes=[201, 202], return_status_code=True) print( - f"{icons.green_dot} Succesfully created the '{report}' report within the '{workspace}' workspace." + f"{icons.green_dot} Succesfully created the '{report}' report within the '{workspace_name}' workspace." ) def update_report_from_reportjson( - report: str, report_json: dict, workspace: Optional[str] = None + report: str, report_json: dict, workspace: Optional[str | UUID] = None ): """ Updates a report based on a report.json file. @@ -134,17 +127,17 @@ def update_report_from_reportjson( Name of the report. report_json : dict The report.json file to be used to update the report. - workspace : str, default=None - The Fabric workspace name in which the report resides. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - report_id = resolve_report_id(report=report, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + report_id = resolve_report_id(report=report, workspace=workspace_id) # Get the existing PBIR file - df_items = get_report_definition(report=report, workspace=workspace) + df_items = get_report_definition(report=report, workspace=workspace_id) df_items_filt = df_items[df_items["path"] == "definition.pbir"] rptDefFile = df_items_filt["payload"].iloc[0] payloadReportJson = _conv_b64(report_json) @@ -175,12 +168,12 @@ def update_report_from_reportjson( lro(client, response, return_status_code=True) print( - f"{icons.green_dot} The '{report}' report within the '{workspace}' workspace has been successfully updated." + f"{icons.green_dot} The '{report}' report within the '{workspace_name}' workspace has been successfully updated." ) def get_report_definition( - report: str, workspace: Optional[str] = None, return_dataframe: bool = True + report: str, workspace: Optional[str | UUID] = None, return_dataframe: bool = True ) -> pd.DataFrame | dict: """ Gets the collection of definition files of a report. @@ -191,8 +184,8 @@ def get_report_definition( ---------- report : str Name of the report. - workspace : str, default=None - The Fabric workspace name in which the report resides. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. return_dataframe : bool, default=True @@ -204,9 +197,9 @@ def get_report_definition( The collection of report definition files within a pandas dataframe. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - report_id = resolve_report_id(report=report, workspace=workspace) + report_id = resolve_report_id(report=report, workspace=workspace_id) client = fabric.FabricRestClient() response = client.post( f"/v1/workspaces/{workspace_id}/reports/{report_id}/getDefinition", @@ -224,7 +217,7 @@ def get_report_definition( def create_model_bpa_report( report: Optional[str] = icons.model_bpa_name, dataset: Optional[str] = icons.model_bpa_name, - dataset_workspace: Optional[str] = None, + dataset_workspace: Optional[str | UUID] = None, ): """ Dynamically generates a Best Practice Analyzer report for analyzing semantic models. @@ -237,24 +230,25 @@ def create_model_bpa_report( dataset : str, default='ModelBPA' Name of the semantic model which feeds this report. Defaults to 'ModelBPA' - dataset_workspace : str, default=None - The Fabric workspace name in which the semantic model resides. + dataset_workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - # from sempy_labs._helper_functions import resolve_dataset_id - dfI = fabric.list_items(workspace=dataset_workspace, type="SemanticModel") + (dataset_workspace_name, dataset_workspace_id) = resolve_workspace_name_and_id(dataset_workspace) + + dfI = fabric.list_items(workspace=dataset_workspace_id, type="SemanticModel") dfI_filt = dfI[dfI["Display Name"] == dataset] if len(dfI_filt) == 0: raise ValueError( - f"The '{dataset}' semantic model does not exist within the '{dataset_workspace}' workspace." + f"The '{dataset}' semantic model does not exist within the '{dataset_workspace_name}' workspace." ) - dfR = fabric.list_reports(workspace=dataset_workspace) + dfR = fabric.list_reports(workspace=dataset_workspace_id) dfR_filt = dfR[dfR["Name"] == report] # dataset_id = resolve_dataset_id(dataset=dataset, workspace=dataset_workspace) @@ -306,14 +300,14 @@ def create_model_bpa_report( if len(dfR_filt) > 0: update_report_from_reportjson( - report=report, report_json=report_json, workspace=dataset_workspace + report=report, report_json=report_json, workspace=dataset_workspace_id ) else: create_report_from_reportjson( report=report, dataset=dataset, report_json=report_json, - workspace=dataset_workspace, + workspace=dataset_workspace_id, ) diff --git a/src/sempy_labs/report/_paginated.py b/src/sempy_labs/report/_paginated.py index 5cc398e..6ccb332 100644 --- a/src/sempy_labs/report/_paginated.py +++ b/src/sempy_labs/report/_paginated.py @@ -2,11 +2,15 @@ from typing import Optional from sempy.fabric.exceptions import FabricHTTPException import pandas as pd +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_workspace_name_and_id, +) def get_report_datasources( report: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> pd.DataFrame: """ Returns a list of data sources for the specified paginated report (RDL) from the specified workspace. @@ -15,8 +19,8 @@ def get_report_datasources( ---------- report : str | List[str] Name(s) of the Power BI report(s). - workspace : str, default=None - The name of the Fabric workspace in which the report resides. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -38,14 +42,10 @@ def get_report_datasources( ] ) - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - else: - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) report_id = fabric.resolve_item_id( - item_name=report, type="PaginatedReport", workspace=workspace + item_name=report, type="PaginatedReport", workspace=workspace_id ) client = fabric.PowerBIRestClient() diff --git a/src/sempy_labs/report/_report_bpa.py b/src/sempy_labs/report/_report_bpa.py index 889797f..53e7d81 100644 --- a/src/sempy_labs/report/_report_bpa.py +++ b/src/sempy_labs/report/_report_bpa.py @@ -11,18 +11,20 @@ resolve_lakehouse_name, resolve_workspace_capacity, _get_max_run_id, + resolve_workspace_name_and_id, ) from sempy_labs.lakehouse import get_lakehouse_tables, lakehouse_attached import sempy_labs._icons as icons from IPython.display import display, HTML import sempy_labs.report._report_helper as helper +from uuid import UUID @log def run_report_bpa( report: str, rules: Optional[pd.DataFrame] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, # language: Optional[str] = None, export: bool = False, return_dataframe: bool = False, @@ -37,8 +39,8 @@ def run_report_bpa( Name of the report. rules : pandas.DataFrame, default=None A pandas dataframe containing rules to be evaluated. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. export : bool, default=False @@ -52,7 +54,9 @@ def run_report_bpa( A pandas dataframe in HTML format showing report objects which violated the best practice analyzer rules. """ - rpt = ReportWrapper(report=report, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + + rpt = ReportWrapper(report=report, workspace=workspace_id) dfCV = rpt.list_custom_visuals() dfP = rpt.list_pages() @@ -145,7 +149,7 @@ def execute_rule(row): df_output["Description"] = row["Description"] df_output["URL"] = row["URL"] df_output["Report URL"] = helper.get_web_url( - report=report, workspace=workspace + report=report, workspace=workspace_id ) page_mapping_dict = dfP.set_index("Page Display Name")["Page URL"].to_dict() @@ -219,13 +223,13 @@ def execute_rule(row): runId = max_run_id + 1 export_df = finalDF.copy() - capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace) + capacity_id, capacity_name = resolve_workspace_capacity(workspace=workspace_id) export_df["Capacity Name"] = capacity_name export_df["Capacity Id"] = capacity_id - export_df["Workspace Name"] = workspace - export_df["Workspace Id"] = fabric.resolve_workspace_id(workspace) + export_df["Workspace Name"] = workspace_name + export_df["Workspace Id"] = workspace_id export_df["Report Name"] = report - export_df["Report Id"] = resolve_report_id(report, workspace) + export_df["Report Id"] = resolve_report_id(report, workspace_id) export_df["RunId"] = runId export_df["Timestamp"] = now export_df["RunId"] = export_df["RunId"].astype(int) diff --git a/src/sempy_labs/report/_report_functions.py b/src/sempy_labs/report/_report_functions.py index 30df40c..187334a 100644 --- a/src/sempy_labs/report/_report_functions.py +++ b/src/sempy_labs/report/_report_functions.py @@ -23,11 +23,12 @@ from sempy._utils._log import log import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def get_report_json( report: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, save_to_file_name: Optional[str] = None, ) -> dict: """ @@ -39,8 +40,8 @@ def get_report_json( ---------- report : str Name of the Power BI report. - workspace : str, default=None - The Fabric workspace name in which the report exists. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the report exists. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. save_to_file_name : str, default=None @@ -52,8 +53,8 @@ def get_report_json( The report.json file for a given Power BI report. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - report_id = resolve_report_id(report=report, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + report_id = resolve_report_id(report=report, workspace=workspace_id) fmt = "PBIR-Legacy" client = fabric.FabricRestClient() @@ -91,24 +92,22 @@ def get_report_json( return report_json -def report_dependency_tree(workspace: Optional[str] = None): +def report_dependency_tree(workspace: Optional[str | UUID] = None): """ Prints a dependency between reports and semantic models. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - if workspace is None: - workspaceId = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspaceId) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfR = fabric.list_reports(workspace=workspace) - dfD = fabric.list_datasets(workspace=workspace) + dfR = fabric.list_reports(workspace=workspace_id) + dfD = fabric.list_datasets(workspace=workspace_id) dfR = pd.merge( dfR, dfD[["Dataset ID", "Dataset Name"]], @@ -124,11 +123,11 @@ def report_dependency_tree(workspace: Optional[str] = None): workspace_icon = "\U0001F465" node_dict = {} - rootNode = Node(workspace) - node_dict[workspace] = rootNode + rootNode = Node(workspace_name) + node_dict[workspace_name] = rootNode rootNode.custom_property = f"{workspace_icon} " - for i, r in dfR.iterrows(): + for _, r in dfR.iterrows(): datasetName = r["Dataset Name"] reportName = r["Report Name"] parentNode = node_dict.get(datasetName) @@ -141,7 +140,7 @@ def report_dependency_tree(workspace: Optional[str] = None): child_node.custom_property = f"{report_icon} " # Print the tree structure - for pre, _, node in RenderTree(node_dict[workspace]): + for pre, _, node in RenderTree(node_dict[workspace_name]): print(f"{pre}{node.custom_property}'{node.name}'") @@ -154,7 +153,7 @@ def export_report( page_name: Optional[str] = None, visual_name: Optional[str] = None, report_filter: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Exports a Power BI report to a file in your lakehouse. @@ -178,22 +177,18 @@ def export_report( The name (GUID) of a visual. If you specify this parameter you must also specify the page_name parameter. report_filter : str, default=None A report filter to be applied when exporting the report. Syntax is user-friendly. See above for examples. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - # https://learn.microsoft.com/rest/api/power-bi/reports/export-to-file-in-group - # https://learn.microsoft.com/rest/api/power-bi/reports/get-export-to-file-status-in-group - # https://learn.microsoft.com/rest/api/power-bi/reports/get-file-of-export-to-file-in-group - if not lakehouse_attached(): raise ValueError( f"{icons.red_dot} In order to run the 'export_report' function, a lakehouse must be attached to the notebook. Please attach a lakehouse to this notebook." ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if isinstance(page_name, str): page_name = [page_name] @@ -242,7 +237,7 @@ def export_report( folderPath = "/lakehouse/default/Files" filePath = os.path.join(folderPath, file_name) - dfI = fabric.list_items(workspace=workspace) + dfI = fabric.list_items(workspace=workspace_id) dfI_filt = dfI[ (dfI["Type"].isin(["Report", "PaginatedReport"])) & (dfI["Display Name"] == report) @@ -250,7 +245,7 @@ def export_report( if len(dfI_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{report}' report does not exist in the '{workspace}' workspace." + f"{icons.red_dot} The '{report}' report does not exist in the '{workspace_name}' workspace." ) reportType = dfI_filt["Type"].iloc[0] @@ -316,13 +311,13 @@ def export_report( request_body = {"format": export_format, "powerBIReportConfiguration": {}} request_body["powerBIReportConfiguration"]["pages"] = [] - dfPage = list_report_pages(report=report, workspace=workspace) + dfPage = list_report_pages(report=report, workspace=workspace_id) for page in page_name: dfPage_filt = dfPage[dfPage["Page ID"] == page] if len(dfPage_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{page}' page does not exist in the '{report}' report within the '{workspace}' workspace." + f"{icons.red_dot} The '{page}' page does not exist in the '{report}' report within the '{workspace_name}' workspace." ) page_dict = {"pageName": page} @@ -338,7 +333,7 @@ def export_report( request_body = {"format": export_format, "powerBIReportConfiguration": {}} request_body["powerBIReportConfiguration"]["pages"] = [] - dfVisual = list_report_visuals(report=report, workspace=workspace) + dfVisual = list_report_visuals(report=report, workspace=workspace_id) a = 0 for page in page_name: visual = visual_name[a] @@ -348,7 +343,7 @@ def export_report( ] if len(dfVisual_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{visual}' visual does not exist on the '{page}' in the '{report}' report within the '{workspace}' workspace." + f"{icons.red_dot} The '{visual}' visual does not exist on the '{page}' in the '{report}' report within the '{workspace_name}' workspace." ) page_dict = {"pageName": page, "visualName": visual} @@ -380,24 +375,24 @@ def export_report( response_body = json.loads(response.content) if response_body["status"] == "Failed": raise ValueError( - f"{icons.red_dot} The export for the '{report}' report within the '{workspace}' workspace in the '{export_format}' format has failed." + f"{icons.red_dot} The export for the '{report}' report within the '{workspace_name}' workspace in the '{export_format}' format has failed." ) else: response = client.get(f"{base_url}/exports/{export_id}/file") print( - f"{icons.in_progress} Saving the '{export_format}' export for the '{report}' report within the '{workspace}' workspace to the lakehouse..." + f"{icons.in_progress} Saving the '{export_format}' export for the '{report}' report within the '{workspace_name}' workspace to the lakehouse..." ) with open(filePath, "wb") as export_file: export_file.write(response.content) print( - f"{icons.green_dot} The '{export_format}' export for the '{report}' report within the '{workspace}' workspace has been saved to the following location: '{filePath}'." + f"{icons.green_dot} The '{export_format}' export for the '{report}' report within the '{workspace_name}' workspace has been saved to the following location: '{filePath}'." ) def clone_report( report: str, cloned_report: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, target_workspace: Optional[str] = None, target_dataset: Optional[str] = None, target_dataset_workspace: Optional[str] = None, @@ -413,8 +408,8 @@ def clone_report( Name of the Power BI report. cloned_report : str Name of the new Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. target_workspace : str, default=None @@ -425,38 +420,36 @@ def clone_report( The name of the semantic model to be used by the cloned report. Defaults to None which resolves to the semantic model used by the initial report. target_dataset_workspace : str, default=None - The workspace in which the semantic model to be used by the report resides. + The workspace name in which the semantic model to be used by the report resides. Defaults to None which resolves to the semantic model used by the initial report. """ - # https://learn.microsoft.com/rest/api/power-bi/reports/clone-report-in-group - - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfI = fabric.list_items(workspace=workspace, type="Report") + dfI = fabric.list_items(workspace=workspace_id, type="Report") dfI_filt = dfI[(dfI["Display Name"] == report)] if len(dfI_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{report}' report does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{report}' report does not exist within the '{workspace_name}' workspace." ) - reportId = resolve_report_id(report, workspace) + reportId = resolve_report_id(report, workspace_id) if target_workspace is None: - target_workspace = workspace + target_workspace = workspace_name target_workspace_id = workspace_id else: target_workspace_id = fabric.resolve_workspace_id(target_workspace) if target_dataset is not None: if target_dataset_workspace is None: - target_dataset_workspace = workspace + target_dataset_workspace = workspace_name target_dataset_id = resolve_dataset_id(target_dataset, target_dataset_workspace) - if report == cloned_report and workspace == target_workspace: + if report == cloned_report and workspace_name == target_workspace: raise ValueError( - f"{icons.warning} The 'report' and 'cloned_report' parameters have the same value of '{report}. The 'workspace' and 'target_workspace' have the same value of '{workspace}'. Either the 'cloned_report' or the 'target_workspace' must be different from the original report." + f"{icons.warning} The 'report' and 'cloned_report' parameters have the same value of '{report}. The 'workspace' and 'target_workspace' have the same value of '{workspace_name}'. Either the 'cloned_report' or the 'target_workspace' must be different from the original report." ) client = fabric.PowerBIRestClient() @@ -464,7 +457,7 @@ def clone_report( request_body = {"name": cloned_report} if target_dataset is not None: request_body["targetModelId"] = target_dataset_id - if target_workspace != workspace: + if target_workspace != workspace_name: request_body["targetWorkspaceId"] = target_workspace_id response = client.post( @@ -478,7 +471,7 @@ def clone_report( ) -def launch_report(report: str, workspace: Optional[str] = None): +def launch_report(report: str, workspace: Optional[str | UUID] = None): """ Shows a Power BI report within a Fabric notebook. @@ -486,8 +479,8 @@ def launch_report(report: str, workspace: Optional[str] = None): ---------- report : str Name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -499,16 +492,14 @@ def launch_report(report: str, workspace: Optional[str] = None): from sempy_labs import resolve_report_id - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - - reportId = resolve_report_id(report, workspace) - - report = Report(group_id=workspace_id, report_id=reportId) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + report_id = resolve_report_id(report, workspace_id) + report = Report(group_id=workspace_id, report_id=report_id) return report -def list_report_pages(report: str, workspace: Optional[str] = None): +def list_report_pages(report: str, workspace: Optional[str | UUID] = None): """ Shows the properties of all pages within a Power BI report. @@ -516,8 +507,8 @@ def list_report_pages(report: str, workspace: Optional[str] = None): ---------- report : str Name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -527,15 +518,13 @@ def list_report_pages(report: str, workspace: Optional[str] = None): A pandas dataframe showing the pages within a Power BI report and their properties. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=["Page ID", "Page Name", "Hidden", "Width", "Height", "Visual Count"] ) - reportJson = get_report_json(report=report, workspace=workspace) + reportJson = get_report_json(report=report, workspace=workspace_id) for section in reportJson["sections"]: pageID = section.get("name") @@ -572,7 +561,7 @@ def list_report_pages(report: str, workspace: Optional[str] = None): return df -def list_report_visuals(report: str, workspace: Optional[str] = None): +def list_report_visuals(report: str, workspace: Optional[str | UUID] = None): """ Shows the properties of all visuals within a Power BI report. @@ -580,8 +569,8 @@ def list_report_visuals(report: str, workspace: Optional[str] = None): ---------- report : str Name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -591,11 +580,9 @@ def list_report_visuals(report: str, workspace: Optional[str] = None): A pandas dataframe showing the visuals within a Power BI report and their properties. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - reportJson = get_report_json(report=report, workspace=workspace) + reportJson = get_report_json(report=report, workspace=workspace_id) df = pd.DataFrame(columns=["Page Name", "Page ID", "Visual ID", "Title"]) @@ -627,7 +614,7 @@ def list_report_visuals(report: str, workspace: Optional[str] = None): return df -def list_report_bookmarks(report: str, workspace: Optional[str] = None): +def list_report_bookmarks(report: str, workspace: Optional[str | UUID] = None): """ Shows the properties of all bookmarks within a Power BI report. @@ -635,8 +622,8 @@ def list_report_bookmarks(report: str, workspace: Optional[str] = None): ---------- report : str Name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -646,9 +633,7 @@ def list_report_bookmarks(report: str, workspace: Optional[str] = None): A pandas dataframe showing the bookmarks within a Power BI report and their properties. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=[ @@ -660,7 +645,7 @@ def list_report_bookmarks(report: str, workspace: Optional[str] = None): ] ) - reportJson = get_report_json(report=report, workspace=workspace) + reportJson = get_report_json(report=report, workspace=workspace_id) reportConfig = reportJson["config"] reportConfigJson = json.loads(reportConfig) @@ -693,7 +678,7 @@ def list_report_bookmarks(report: str, workspace: Optional[str] = None): } df = pd.concat([df, pd.DataFrame(new_data, index=[0])], ignore_index=True) - listPages = list_report_pages(report=report, workspace=workspace) + listPages = list_report_pages(report=report, workspace=workspace_id) df = pd.merge(df, listPages[["Page ID", "Page Name"]], on="Page ID", how="left") df = df[ @@ -711,13 +696,13 @@ def list_report_bookmarks(report: str, workspace: Optional[str] = None): except Exception: print( - f"The '{report}' report within the '{workspace}' workspace has no bookmarks." + f"The '{report}' report within the '{workspace_name}' workspace has no bookmarks." ) @log def translate_report_titles( - report: str, languages: Union[str, List[str]], workspace: Optional[str] = None + report: str, languages: Union[str, List[str]], workspace: Optional[str | UUID] = None ): """ Dynamically generates new Power BI reports which have report titles translated into the specified language(s). @@ -728,22 +713,24 @@ def translate_report_titles( Name of the Power BI report. languages : str, List[str] The language code(s) in which to translate the report titles. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ from synapse.ml.services import Translate from pyspark.sql import SparkSession + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + if isinstance(languages, str): languages = [languages] for lang in languages: language_validate(lang) - reportJson = get_report_json(report=report, workspace=workspace) - dfV = list_report_visuals(report=report, workspace=workspace) + reportJson = get_report_json(report=report, workspace=workspace_id) + dfV = list_report_visuals(report=report, workspace=workspace_id) spark = SparkSession.builder.getOrCreate() df = spark.createDataFrame(dfV) columnToTranslate = "Title" @@ -771,7 +758,7 @@ def translate_report_titles( language = language_validate(lang) clonedReportName = f"{report}_{language}" - dfRep = fabric.list_reports(workspace=workspace) + dfRep = fabric.list_reports(workspace=workspace_id) dfRep_filt = dfRep[ (dfRep["Name"] == clonedReportName) & (dfRep["Report Type"] == "PowerBIReport") @@ -779,14 +766,14 @@ def translate_report_titles( if len(dfRep_filt) > 0: print( - f"{icons.yellow_dot} The '{clonedReportName}' report already exists in the '{workspace} workspace." + f"{icons.yellow_dot} The '{clonedReportName}' report already exists in the '{workspace_name} workspace." ) else: clone_report( - report=report, cloned_report=clonedReportName, workspace=workspace + report=report, cloned_report=clonedReportName, workspace=workspace_id ) print( - f"{icons.green_dot} The '{clonedReportName}' report has been created via clone in the '{workspace} workspace." + f"{icons.green_dot} The '{clonedReportName}' report has been created via clone in the '{workspace_name} workspace." ) rptJsonTr = copy.deepcopy(reportJson) @@ -816,8 +803,8 @@ def translate_report_titles( # Post updated report json file to cloned report update_report_from_reportjson( - report=clonedReportName, report_json=rptJsonTr, workspace=workspace + report=clonedReportName, report_json=rptJsonTr, workspace=workspace_id ) print( - f"{icons.green_dot} The visual titles within the '{clonedReportName}' report within the '{workspace}' have been translated into '{language}' accordingly." + f"{icons.green_dot} The visual titles within the '{clonedReportName}' report within the '{workspace_name}' have been translated into '{language}' accordingly." ) diff --git a/src/sempy_labs/report/_report_helper.py b/src/sempy_labs/report/_report_helper.py index 4ae7470..a3970bd 100644 --- a/src/sempy_labs/report/_report_helper.py +++ b/src/sempy_labs/report/_report_helper.py @@ -5,6 +5,10 @@ import base64 import json import requests +from uuid import UUID +from sempy_labs._helper_functions import ( + resolve_workspace_name_and_id, +) vis_type_mapping = { @@ -66,16 +70,16 @@ page_types = ["Tooltip", "Letter", "4:3", "16:9"] -def get_web_url(report: str, workspace: Optional[str] = None): +def get_web_url(report: str, workspace: Optional[str | UUID] = None): - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfR = fabric.list_reports(workspace=workspace) + dfR = fabric.list_reports(workspace=workspace_id) dfR_filt = dfR[dfR["Name"] == report] if len(dfR_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{report}' report does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{report}' report does not exist within the '{workspace_name}' workspace." ) web_url = dfR_filt["Web Url"].iloc[0] diff --git a/src/sempy_labs/report/_report_list_functions.py b/src/sempy_labs/report/_report_list_functions.py index 2521e0d..bb5e567 100644 --- a/src/sempy_labs/report/_report_list_functions.py +++ b/src/sempy_labs/report/_report_list_functions.py @@ -3,13 +3,16 @@ import pandas as pd from sempy_labs._helper_functions import ( format_dax_object_name, + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, ) from sempy_labs.report._reportwrapper import ReportWrapper from sempy_labs._list_functions import list_reports_using_semantic_model +from uuid import UUID def list_unused_objects_in_reports( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of all columns in the semantic model which are not used in any related Power BI reports (including dependencies). @@ -17,10 +20,10 @@ def list_unused_objects_in_reports( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -32,7 +35,10 @@ def list_unused_objects_in_reports( # TODO: what about relationships/RLS? - dfR = _list_all_report_semantic_model_objects(dataset=dataset, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + + dfR = _list_all_report_semantic_model_objects(dataset=dataset_id, workspace=workspace_id) dfR_filt = ( dfR[dfR["Object Type"] == "Column"][["Table Name", "Object Name"]] .drop_duplicates() @@ -42,7 +48,7 @@ def list_unused_objects_in_reports( dfR_filt["Table Name"], dfR_filt["Object Name"] ) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id) dfC["Column Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"]) df = dfC[~(dfC["Column Object"].isin(dfR_filt["Column Object"].values))] @@ -52,7 +58,7 @@ def list_unused_objects_in_reports( def _list_all_report_semantic_model_objects( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a unique list of all semantic model objects (columns, measures, hierarchies) which are used in all reports which leverage the semantic model. @@ -60,10 +66,10 @@ def _list_all_report_semantic_model_objects( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -73,7 +79,10 @@ def _list_all_report_semantic_model_objects( A pandas dataframe. """ - dfR = list_reports_using_semantic_model(dataset=dataset, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + + dfR = list_reports_using_semantic_model(dataset=dataset_id, workspace=workspace_id) dfs = [] for _, r in dfR.iterrows(): diff --git a/src/sempy_labs/report/_report_rebind.py b/src/sempy_labs/report/_report_rebind.py index c561a18..f797ab3 100644 --- a/src/sempy_labs/report/_report_rebind.py +++ b/src/sempy_labs/report/_report_rebind.py @@ -1,20 +1,21 @@ import sempy.fabric as fabric from sempy_labs._helper_functions import ( resolve_dataset_id, + resolve_workspace_name_and_id, resolve_report_id, ) from typing import Optional, List from sempy._utils._log import log import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException - +from uuid import UUID @log def report_rebind( report: str | List[str], dataset: str, - report_workspace: Optional[str] = None, - dataset_workspace: Optional[str] = None, + report_workspace: Optional[str | UUID] = None, + dataset_workspace: Optional[str | UUID] = None, ): """ Rebinds a report to a semantic model. @@ -27,23 +28,20 @@ def report_rebind( Name(s) of the Power BI report(s). dataset : str Name of the semantic model. - report_workspace : str, default=None - The name of the Fabric workspace in which the report resides. + report_workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. - dataset_workspace : str, default=None - The name of the Fabric workspace in which the semantic model resides. + dataset_workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which the semantic model resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - if report_workspace is None: - report_workspace_id = fabric.get_workspace_id() - report_workspace = fabric.resolve_workspace_name(report_workspace_id) - else: - report_workspace_id = fabric.resolve_workspace_id(report_workspace) + (report_workspace_name, report_workspace_id) = resolve_workspace_name_and_id(report_workspace) + if dataset_workspace is None: - dataset_workspace = report_workspace + dataset_workspace = report_workspace_name client = fabric.PowerBIRestClient() @@ -51,14 +49,14 @@ def report_rebind( report = [report] for rpt in report: - reportId = resolve_report_id(report=rpt, workspace=report_workspace) - datasetId = resolve_dataset_id(dataset=dataset, workspace=dataset_workspace) + report_id = resolve_report_id(report=rpt, workspace=report_workspace_id) + dataset_id = resolve_dataset_id(dataset=dataset, workspace=dataset_workspace) # Prepare API - request_body = {"datasetId": datasetId} + request_body = {"datasetId": dataset_id} response = client.post( - f"/v1.0/myorg/groups/{report_workspace_id}/reports/{reportId}/Rebind", + f"/v1.0/myorg/groups/{report_workspace_id}/reports/{report_id}/Rebind", json=request_body, ) diff --git a/src/sempy_labs/report/_reportwrapper.py b/src/sempy_labs/report/_reportwrapper.py index 0a59594..98d281c 100644 --- a/src/sempy_labs/report/_reportwrapper.py +++ b/src/sempy_labs/report/_reportwrapper.py @@ -8,6 +8,7 @@ _add_part, lro, _decode_b64, + resolve_workspace_name_and_id, ) from typing import Optional, List import pandas as pd @@ -32,8 +33,8 @@ class ReportWrapper: ---------- report : str The name of the report. - workspace : str - The name of the workspace in which the report resides. + workspace : str | UUID + The name or ID of the workspace in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -50,7 +51,7 @@ class ReportWrapper: def __init__( self, report: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Connects to a Power BI report and retrieves its definition. @@ -61,8 +62,8 @@ def __init__( ---------- report : str The name of the report. - workspace : str - The name of the workspace in which the report resides. + workspace : str | UUID + The name or ID of the workspace in which the report resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -77,11 +78,10 @@ def __init__( warnings.simplefilter(action="ignore", category=FutureWarning) self._report = report - self._workspace = workspace - self._workspace_id = fabric.resolve_workspace_id(workspace) - self._report_id = resolve_report_id(report, workspace) + (self._workspace_name, self._workspace_id) = resolve_workspace_name_and_id(workspace) + self._report_id = resolve_report_id(report, self._workspace_id) self.rdef = get_report_definition( - report=self._report, workspace=self._workspace + report=self._report, workspace=self._workspace_id ) if len(self.rdef[self.rdef["path"] == "definition/report.json"]) == 0: @@ -95,12 +95,12 @@ def _add_extended(self, dataframe): from sempy_labs.tom import connect_semantic_model - dataset_id, dataset_name, dataset_workspace_id, dataset_workspace = ( - resolve_dataset_from_report(report=self._report, workspace=self._workspace) + dataset_id, dataset_name, dataset_workspace_id, dataset_workspace_name = ( + resolve_dataset_from_report(report=self._report, workspace=self._workspace_id) ) with connect_semantic_model( - dataset=dataset_name, readonly=True, workspace=dataset_workspace + dataset=dataset_id, readonly=True, workspace=dataset_workspace_id ) as tom: for index, row in dataframe.iterrows(): obj_type = row["Object Type"] @@ -218,7 +218,7 @@ def get_theme(self, theme_type: str = "baseTheme") -> dict: theme_collection = rptJson.get("themeCollection", {}) if theme_type not in theme_collection: raise ValueError( - f"{icons.red_dot} The {self._report} report within the '{self._workspace} workspace has no custom theme." + f"{icons.red_dot} The {self._report} report within the '{self._workspace_name} workspace has no custom theme." ) ct = theme_collection.get(theme_type) theme_name = ct["name"] @@ -413,7 +413,7 @@ def list_page_filters(self, extended: bool = False) -> pd.DataFrame: ) df["Page URL"] = df["Page Name"].apply( - lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{page_name}" + lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace_id)}/{page_name}" ) bool_cols = ["Hidden", "Locked", "Used"] @@ -691,7 +691,7 @@ def list_pages(self) -> pd.DataFrame: df[bool_cols] = df[bool_cols].astype(bool) df["Page URL"] = df["Page Name"].apply( - lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace)}/{page_name}" + lambda page_name: f"{helper.get_web_url(report=self._report, workspace=self._workspace_id)}/{page_name}" ) return df @@ -1170,9 +1170,9 @@ def list_semantic_model_objects(self, extended: bool = False) -> pd.DataFrame: ) if extended: - dataset_id, dataset_name, dataset_workspace_id, dataset_workspace = ( + dataset_id, dataset_name, dataset_workspace_id, dataset_workspace_name = ( resolve_dataset_from_report( - report=self._report, workspace=self._workspace + report=self._report, workspace=self._workspace_id ) ) @@ -1195,7 +1195,7 @@ def check_validity(tom, row): return object_validators.get(row["Object Type"], lambda: False)() with connect_semantic_model( - dataset=dataset_name, readonly=True, workspace=dataset_workspace + dataset=dataset_id, readonly=True, workspace=dataset_workspace_id ) as tom: df["Valid Semantic Model Object"] = df.apply( lambda row: check_validity(tom, row), axis=1 @@ -1214,11 +1214,11 @@ def _list_all_semantic_model_objects(self): .drop_duplicates() .reset_index(drop=True) ) - dataset_id, dataset_name, dataset_workspace_id, dataset_workspace = ( - resolve_dataset_from_report(report=self._report, workspace=self._workspace) + dataset_id, dataset_name, dataset_workspace_id, dataset_workspace_name = ( + resolve_dataset_from_report(report=self._report, workspace=self._workspace_id) ) dep = get_measure_dependencies( - dataset=dataset_name, workspace=dataset_workspace + dataset=dataset_id, workspace=dataset_workspace_id ) rpt_measures = df[df["Object Type"] == "Measure"]["Object Name"].values new_rows = dep[dep["Object Name"].isin(rpt_measures)][ @@ -1232,7 +1232,7 @@ def _list_all_semantic_model_objects(self): ) result_df["Dataset Name"] = dataset_name - result_df["Dataset Workspace Name"] = dataset_workspace + result_df["Dataset Workspace Name"] = dataset_workspace_name colName = "Dataset Name" result_df.insert(0, colName, result_df.pop(colName)) colName = "Dataset Workspace Name" @@ -1539,7 +1539,7 @@ def set_theme(self, theme_file_path: str): self.update_report(request_body=request_body) print( - f"{icons.green_dot} The '{theme_name}' theme has been set as the theme for the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} The '{theme_name}' theme has been set as the theme for the '{self._report}' report within the '{self._workspace_name}' workspace." ) def set_active_page(self, page_name: str): @@ -1567,7 +1567,7 @@ def set_active_page(self, page_name: str): self._update_single_file(file_name=pages_file, new_payload=file_payload) print( - f"{icons.green_dot} The '{page_display_name}' page has been set as the active page in the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} The '{page_display_name}' page has been set as the active page in the '{self._report}' report within the '{self._workspace_name}' workspace." ) def set_page_type(self, page_name: str, page_type: str): @@ -1640,7 +1640,7 @@ def remove_unnecessary_custom_visuals(self): cv_remove_display.append(cv_display) if len(cv_remove) == 0: print( - f"{icons.green_dot} There are no unnecessary custom visuals in the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} There are no unnecessary custom visuals in the '{self._report}' report within the '{self._workspace_name}' workspace." ) return @@ -1662,7 +1662,7 @@ def remove_unnecessary_custom_visuals(self): self.update_report(request_body=request_body) print( - f"{icons.green_dot} The {cv_remove_display} custom visuals have been removed from the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} The {cv_remove_display} custom visuals have been removed from the '{self._report}' report within the '{self._workspace_name}' workspace." ) def migrate_report_level_measures(self, measures: Optional[str | List[str]] = None): @@ -1681,12 +1681,12 @@ def migrate_report_level_measures(self, measures: Optional[str | List[str]] = No rlm = self.list_report_level_measures() if len(rlm) == 0: print( - f"{icons.green_dot} The '{self._report}' report within the '{self._workspace}' workspace has no report-level measures." + f"{icons.green_dot} The '{self._report}' report within the '{self._workspace_name}' workspace has no report-level measures." ) return - dataset_id, dataset_name, dataset_workspace_id, dataset_workspace = ( - resolve_dataset_from_report(report=self._report, workspace=self._workspace) + dataset_id, dataset_name, dataset_workspace_id, dataset_workspace_name = ( + resolve_dataset_from_report(report=self._report, workspace=self._workspace_id) ) if isinstance(measures, str): @@ -1703,7 +1703,7 @@ def migrate_report_level_measures(self, measures: Optional[str | List[str]] = No mCount = 0 with connect_semantic_model( - dataset=dataset_name, readonly=False, workspace=dataset_workspace + dataset=dataset_id, readonly=False, workspace=dataset_workspace_id ) as tom: for _, r in rlm.iterrows(): tableName = r["Table Name"] @@ -1748,7 +1748,7 @@ def migrate_report_level_measures(self, measures: Optional[str | List[str]] = No self.update_report(request_body=request_body) print( - f"{icons.green_dot} The report-level measures have been migrated to the '{dataset_name}' semantic model within the '{dataset_workspace}' workspace." + f"{icons.green_dot} The report-level measures have been migrated to the '{dataset_name}' semantic model within the '{dataset_workspace_name}' workspace." ) def set_page_visibility(self, page_name: str, hidden: bool): @@ -1798,7 +1798,7 @@ def hide_tooltip_drillthrough_pages(self): if len(dfP_filt) == 0: print( - f"{icons.green_dot} There are no Tooltip or Drillthrough pages in the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} There are no Tooltip or Drillthrough pages in the '{self._report}' report within the '{self._workspace_name}' workspace." ) return @@ -1837,7 +1837,7 @@ def delete_key_in_json(obj, key_to_delete): self.update_report(request_body=request_body) print( - f"{icons.green_dot} Show items with data has been disabled for all visuals in the '{self._report}' report within the '{self._workspace}' workspace." + f"{icons.green_dot} Show items with data has been disabled for all visuals in the '{self._report}' report within the '{self._workspace_name}' workspace." ) # Set Annotations From 9fc658f4e7b5287baeacfc270abb273eb6c4d62d Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 14:42:30 +0200 Subject: [PATCH 5/6] uuid --- src/sempy_labs/_data_pipelines.py | 40 ++-- src/sempy_labs/_dataflows.py | 40 ++-- src/sempy_labs/_dax.py | 53 ++--- src/sempy_labs/_environments.py | 44 ++--- src/sempy_labs/_eventhouses.py | 31 +-- src/sempy_labs/_eventstreams.py | 31 +-- src/sempy_labs/_external_data_shares.py | 37 ++-- src/sempy_labs/_generate_semantic_model.py | 132 +++++++------ src/sempy_labs/_git.py | 73 +++---- src/sempy_labs/_helper_functions.py | 192 ++++++++----------- src/sempy_labs/_kql_databases.py | 31 +-- src/sempy_labs/_kql_querysets.py | 31 +-- src/sempy_labs/_list_functions.py | 192 ++++++++++--------- src/sempy_labs/_managed_private_endpoints.py | 32 ++-- src/sempy_labs/_mirrored_databases.py | 97 +++++----- src/sempy_labs/_mirrored_warehouses.py | 9 +- src/sempy_labs/_ml_experiments.py | 31 +-- src/sempy_labs/_ml_models.py | 29 +-- src/sempy_labs/_model_dependencies.py | 41 ++-- src/sempy_labs/_notebooks.py | 50 ++--- src/sempy_labs/_one_lake_integration.py | 39 ++-- src/sempy_labs/_query_scale_out.py | 128 +++++++------ src/sempy_labs/_refresh_semantic_model.py | 39 ++-- src/sempy_labs/_spark.py | 65 +++---- src/sempy_labs/_sql.py | 9 +- src/sempy_labs/_translations.py | 19 +- src/sempy_labs/_warehouses.py | 43 +++-- src/sempy_labs/_workspace_identity.py | 21 +- src/sempy_labs/_workspaces.py | 67 +++---- src/sempy_labs/admin/_scanner.py | 2 + 30 files changed, 829 insertions(+), 819 deletions(-) diff --git a/src/sempy_labs/_data_pipelines.py b/src/sempy_labs/_data_pipelines.py index 3062a6c..8d165bd 100644 --- a/src/sempy_labs/_data_pipelines.py +++ b/src/sempy_labs/_data_pipelines.py @@ -9,9 +9,10 @@ _decode_b64, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_data_pipelines(workspace: Optional[str] = None) -> pd.DataFrame: +def list_data_pipelines(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the data pipelines within a workspace. @@ -19,8 +20,8 @@ def list_data_pipelines(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -32,7 +33,7 @@ def list_data_pipelines(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["Data Pipeline Name", "Data Pipeline ID", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/dataPipelines") @@ -54,7 +55,7 @@ def list_data_pipelines(workspace: Optional[str] = None) -> pd.DataFrame: def create_data_pipeline( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric data pipeline. @@ -67,13 +68,13 @@ def create_data_pipeline( Name of the data pipeline. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -88,11 +89,11 @@ def create_data_pipeline( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' data pipeline has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' data pipeline has been created within the '{workspace_name}' workspace." ) -def delete_data_pipeline(name: str, workspace: Optional[str] = None): +def delete_data_pipeline(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric data pipeline. @@ -102,16 +103,16 @@ def delete_data_pipeline(name: str, workspace: Optional[str] = None): ---------- name: str Name of the data pipeline. - workspace : str, default=None + workspace : str | UUID, default=None The Fabric workspace name. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="DataPipeline", workspace=workspace + item_name=name, type="DataPipeline", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -121,12 +122,12 @@ def delete_data_pipeline(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' data pipeline within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' data pipeline within the '{workspace_name}' workspace has been deleted." ) def get_data_pipeline_definition( - name: str, workspace: Optional[str] = None, decode: bool = True + name: str, workspace: Optional[str | UUID] = None, decode: bool = True ) -> dict | pd.DataFrame: """ Obtains the definition of a data pipeline. @@ -135,8 +136,8 @@ def get_data_pipeline_definition( ---------- name : str The name of the data pipeline. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. decode : bool, default=True @@ -150,10 +151,9 @@ def get_data_pipeline_definition( A pandas dataframe showing the data pipelines within a workspace. """ - workspace = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="DataPipeline", workspace=workspace + item_name=name, type="DataPipeline", workspace=workspace_id ) client = fabric.FabricRestClient() diff --git a/src/sempy_labs/_dataflows.py b/src/sempy_labs/_dataflows.py index 2055879..5c6b2cb 100644 --- a/src/sempy_labs/_dataflows.py +++ b/src/sempy_labs/_dataflows.py @@ -10,14 +10,14 @@ from uuid import UUID -def list_dataflows(workspace: Optional[str] = None): +def list_dataflows(workspace: Optional[str | UUID] = None): """ Shows a list of all dataflows which exist within a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -27,7 +27,7 @@ def list_dataflows(workspace: Optional[str] = None): A pandas dataframe showing the dataflows which exist within a workspace. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.PowerBIRestClient() response = client.get(f"/v1.0/myorg/groups/{workspace_id}/dataflows") if response.status_code != 200: @@ -57,7 +57,7 @@ def list_dataflows(workspace: Optional[str] = None): def assign_workspace_to_dataflow_storage( - dataflow_storage_account: str, workspace: Optional[str] = None + dataflow_storage_account: str, workspace: Optional[str | UUID] = None ): """ Assigns a dataflow storage account to a workspace. @@ -68,13 +68,13 @@ def assign_workspace_to_dataflow_storage( ---------- dataflow_storage_account : str The name of the dataflow storage account. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = list_dataflow_storage_accounts() df_filt = df[df["Dataflow Storage Account Name"] == dataflow_storage_account] @@ -96,7 +96,7 @@ def assign_workspace_to_dataflow_storage( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{dataflow_storage_account}' dataflow storage account has been assigned to the '{workspace}' workspacce." + f"{icons.green_dot} The '{dataflow_storage_account}' dataflow storage account has been assigned to the '{workspace_name}' workspacce." ) @@ -138,7 +138,7 @@ def list_dataflow_storage_accounts() -> pd.DataFrame: def list_upstream_dataflows( - dataflow: str | UUID, workspace: Optional[str] = None + dataflow: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of upstream dataflows for the specified dataflow. @@ -149,8 +149,8 @@ def list_upstream_dataflows( ---------- dataflow : str | UUID Name or UUID of the dataflow. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -160,10 +160,9 @@ def list_upstream_dataflows( A pandas dataframe showing a list of upstream dataflows for the specified dataflow. """ - workspace_name = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) (dataflow_name, dataflow_id) = _resolve_dataflow_name_and_id( - dataflow=dataflow, workspace=workspace + dataflow=dataflow, workspace=workspace_id ) client = fabric.PowerBIRestClient() @@ -195,7 +194,7 @@ def collect_upstreams( tgt_workspace_id = v.get("groupId") tgt_workspace_name = fabric.resolve_workspace_name(tgt_workspace_id) (tgt_dataflow_name, _) = _resolve_dataflow_name_and_id( - dataflow=tgt_dataflow_id, workspace=tgt_workspace_name + dataflow=tgt_dataflow_id, workspace=tgt_workspace_id ) df.loc[len(df)] = { @@ -223,13 +222,12 @@ def collect_upstreams( def _resolve_dataflow_name_and_id( - dataflow: str | UUID, workspace: Optional[str] = None + dataflow: str | UUID, workspace: Optional[str | UUID] = None ) -> Tuple[str, UUID]: - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfD = list_dataflows(workspace=workspace) + dfD = list_dataflows(workspace=workspace_id) if _is_valid_uuid(dataflow): dfD_filt = dfD[dfD["Dataflow Id"] == dataflow] @@ -238,7 +236,7 @@ def _resolve_dataflow_name_and_id( if len(dfD_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{dataflow}' dataflow does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{dataflow}' dataflow does not exist within the '{workspace_name}' workspace." ) dataflow_id = dfD_filt["Dataflow Id"].iloc[0] diff --git a/src/sempy_labs/_dax.py b/src/sempy_labs/_dax.py index d6434a6..3be3417 100644 --- a/src/sempy_labs/_dax.py +++ b/src/sempy_labs/_dax.py @@ -1,22 +1,23 @@ import sempy.fabric as fabric import pandas as pd from sempy_labs._helper_functions import ( - resolve_dataset_id, resolve_workspace_name_and_id, format_dax_object_name, + resolve_dataset_name_and_id, ) from sempy_labs._model_dependencies import get_model_calc_dependencies from typing import Optional, List from sempy._utils._log import log from tqdm.auto import tqdm +from uuid import UUID @log def evaluate_dax_impersonation( - dataset: str, + dataset: str | UUID, dax_query: str, user_name: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Runs a DAX query against a semantic model using the `REST API `_. @@ -27,13 +28,13 @@ def evaluate_dax_impersonation( Parameters ---------- dataset : str - Name of the semantic model. + Name or ID of the semantic model. dax_query : str The DAX query. user_name : str The user name (i.e. hello@goodbye.com). workspace : str, default=None - The Fabric workspace name. + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -43,8 +44,8 @@ def evaluate_dax_impersonation( A pandas dataframe holding the result of the DAX query. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) request_body = { "queries": [{"query": dax_query}], @@ -66,11 +67,11 @@ def evaluate_dax_impersonation( @log def get_dax_query_dependencies( - dataset: str, + dataset: str | UUID, dax_string: str | List[str], put_in_memory: bool = False, show_vertipaq_stats: bool = True, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> pd.DataFrame: """ Obtains the columns on which a DAX query depends, including model dependencies. Shows Vertipaq statistics (i.e. Total Size, Data Size, Dictionary Size, Hierarchy Size) for easy prioritizing. @@ -78,7 +79,7 @@ def get_dax_query_dependencies( Parameters ---------- dataset : str - Name of the semantic model. + Name or ID of the semantic model. dax_string : str | List[str] The DAX query or list of DAX queries. put_in_memory : bool, default=False @@ -86,7 +87,7 @@ def get_dax_query_dependencies( show_vertipaq_stats : bool, default=True If True, shows vertipaq stats (i.e. Total Size, Data Size, Dictionary Size, Hierarchy Size) workspace : str, default=None - The Fabric workspace name. + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -96,15 +97,15 @@ def get_dax_query_dependencies( A pandas dataframe showing the dependent columns of a given DAX query including model dependencies. """ - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) if isinstance(dax_string, str): dax_string = [dax_string] final_df = pd.DataFrame(columns=["Object Type", "Table", "Object"]) - cd = get_model_calc_dependencies(dataset=dataset, workspace=workspace) + cd = get_model_calc_dependencies(dataset=dataset_id, workspace=workspace_id) for dax in dax_string: # Escape quotes in dax @@ -121,7 +122,7 @@ def get_dax_query_dependencies( RETURN all_dependencies """ dep = fabric.evaluate_dax( - dataset=dataset, workspace=workspace, dax_string=final_query + dataset=dataset_id, workspace=workspace_id, dax_string=final_query ) # Clean up column names and values (remove outside square brackets, underscorees in object type) @@ -168,7 +169,7 @@ def get_dax_query_dependencies( final_df["Full Object"] = format_dax_object_name( final_df["Table Name"], final_df["Column Name"] ) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace, extended=True) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id, extended=True) dfC["Full Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"]) dfC_filtered = dfC[dfC["Full Object"].isin(final_df["Full Object"].values)][ @@ -202,12 +203,12 @@ def get_dax_query_dependencies( ) dax = f"""EVALUATE TOPN(1,SUMMARIZECOLUMNS({css}))""" fabric.evaluate_dax( - dataset=dataset, dax_string=dax, workspace=workspace + dataset=dataset_id, dax_string=dax, workspace=workspace_id ) # Get column stats again dfC = fabric.list_columns( - dataset=dataset, workspace=workspace, extended=True + dataset=dataset_id, workspace=workspace_id, extended=True ) dfC["Full Object"] = format_dax_object_name( dfC["Table Name"], dfC["Column Name"] @@ -233,19 +234,19 @@ def get_dax_query_dependencies( @log def get_dax_query_memory_size( - dataset: str, dax_string: str, workspace: Optional[str] = None + dataset: str | UUID, dax_string: str, workspace: Optional[str | UUID] = None ) -> int: """ Obtains the total size, in bytes, used by all columns that a DAX query depends on. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. dax_string : str The DAX query. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -255,11 +256,11 @@ def get_dax_query_memory_size( The total size, in bytes, used by all columns that the DAX query depends on. """ - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) df = get_dax_query_dependencies( - dataset=dataset, workspace=workspace, dax_string=dax_string, put_in_memory=True + dataset=dataset_id, workspace=workspace_id, dax_string=dax_string, put_in_memory=True ) return df["Total Size"].sum() diff --git a/src/sempy_labs/_environments.py b/src/sempy_labs/_environments.py index 58e001d..e67c530 100644 --- a/src/sempy_labs/_environments.py +++ b/src/sempy_labs/_environments.py @@ -8,10 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException - +from uuid import UUID def create_environment( - environment: str, description: Optional[str] = None, workspace: Optional[str] = None + environment: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric environment. @@ -24,13 +24,13 @@ def create_environment( Name of the environment. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": environment} @@ -45,11 +45,11 @@ def create_environment( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{environment}' environment has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{environment}' environment has been created within the '{workspace_name}' workspace." ) -def list_environments(workspace: Optional[str] = None) -> pd.DataFrame: +def list_environments(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the environments within a workspace. @@ -57,8 +57,8 @@ def list_environments(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -70,7 +70,7 @@ def list_environments(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["Environment Name", "Environment Id", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/environments") @@ -91,7 +91,7 @@ def list_environments(workspace: Optional[str] = None) -> pd.DataFrame: return df -def delete_environment(environment: str, workspace: Optional[str] = None): +def delete_environment(environment: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric environment. @@ -101,17 +101,17 @@ def delete_environment(environment: str, workspace: Optional[str] = None): ---------- environment: str Name of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ from sempy_labs._helper_functions import resolve_environment_id - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) environment_id = resolve_environment_id( - environment=environment, workspace=workspace + environment=environment, workspace=workspace_id ) client = fabric.FabricRestClient() @@ -123,11 +123,11 @@ def delete_environment(environment: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{environment}' environment within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{environment}' environment within the '{workspace_name}' workspace has been deleted." ) -def publish_environment(environment: str, workspace: Optional[str] = None): +def publish_environment(environment: str, workspace: Optional[str | UUID] = None): """ Publishes a Fabric environment. @@ -135,8 +135,8 @@ def publish_environment(environment: str, workspace: Optional[str] = None): ---------- environment: str Name of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -145,9 +145,9 @@ def publish_environment(environment: str, workspace: Optional[str] = None): from sempy_labs._helper_functions import resolve_environment_id - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) environment_id = resolve_environment_id( - environment=environment, workspace=workspace + environment=environment, workspace=workspace_id ) client = fabric.FabricRestClient() @@ -158,5 +158,5 @@ def publish_environment(environment: str, workspace: Optional[str] = None): lro(client, response) print( - f"{icons.green_dot} The '{environment}' environment within the '{workspace}' workspace has been published." + f"{icons.green_dot} The '{environment}' environment within the '{workspace_name}' workspace has been published." ) diff --git a/src/sempy_labs/_eventhouses.py b/src/sempy_labs/_eventhouses.py index 8ef4269..2be48a1 100644 --- a/src/sempy_labs/_eventhouses.py +++ b/src/sempy_labs/_eventhouses.py @@ -8,10 +8,11 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def create_eventhouse( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric eventhouse. @@ -24,13 +25,13 @@ def create_eventhouse( Name of the eventhouse. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -45,11 +46,11 @@ def create_eventhouse( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' eventhouse has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' eventhouse has been created within the '{workspace_name}' workspace." ) -def list_eventhouses(workspace: Optional[str] = None) -> pd.DataFrame: +def list_eventhouses(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the eventhouses within a workspace. @@ -57,8 +58,8 @@ def list_eventhouses(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -70,7 +71,7 @@ def list_eventhouses(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["Eventhouse Name", "Eventhouse Id", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/eventhouses") @@ -91,7 +92,7 @@ def list_eventhouses(workspace: Optional[str] = None) -> pd.DataFrame: return df -def delete_eventhouse(name: str, workspace: Optional[str] = None): +def delete_eventhouse(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric eventhouse. @@ -101,16 +102,16 @@ def delete_eventhouse(name: str, workspace: Optional[str] = None): ---------- name: str Name of the eventhouse. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="Eventhouse", workspace=workspace + item_name=name, type="Eventhouse", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -120,5 +121,5 @@ def delete_eventhouse(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' eventhouse within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' eventhouse within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_eventstreams.py b/src/sempy_labs/_eventstreams.py index b13e8b9..c0f90c7 100644 --- a/src/sempy_labs/_eventstreams.py +++ b/src/sempy_labs/_eventstreams.py @@ -8,9 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_eventstreams(workspace: Optional[str] = None) -> pd.DataFrame: +def list_eventstreams(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the eventstreams within a workspace. @@ -18,8 +19,8 @@ def list_eventstreams(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -31,7 +32,7 @@ def list_eventstreams(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["Eventstream Name", "Eventstream Id", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/eventstreams") @@ -53,7 +54,7 @@ def list_eventstreams(workspace: Optional[str] = None) -> pd.DataFrame: def create_eventstream( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric eventstream. @@ -66,13 +67,13 @@ def create_eventstream( Name of the eventstream. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -87,11 +88,11 @@ def create_eventstream( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' eventstream has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' eventstream has been created within the '{workspace_name}' workspace." ) -def delete_eventstream(name: str, workspace: Optional[str] = None): +def delete_eventstream(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric eventstream. @@ -101,16 +102,16 @@ def delete_eventstream(name: str, workspace: Optional[str] = None): ---------- name: str Name of the eventstream. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="Eventstream", workspace=workspace + item_name=name, type="Eventstream", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -120,5 +121,5 @@ def delete_eventstream(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' eventstream within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' eventstream within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_external_data_shares.py b/src/sempy_labs/_external_data_shares.py index 7bd111d..7c163bc 100644 --- a/src/sempy_labs/_external_data_shares.py +++ b/src/sempy_labs/_external_data_shares.py @@ -8,6 +8,7 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def create_external_data_share( @@ -15,7 +16,7 @@ def create_external_data_share( item_type: str, paths: str | List[str], recipient: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates an external data share for a given path or list of paths in the specified item. @@ -32,17 +33,15 @@ def create_external_data_share( The path or list of paths that are to be externally shared. Currently, only a single path is supported. recipient : str The email address of the recipient. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - # https://learn.microsoft.com/en-us/rest/api/fabric/core/external-data-shares/create-external-data-share?tabs=HTTP - - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=item_name, type=item_type, workspace=workspace + item_name=item_name, type=item_type, workspace=workspace_id ) if isinstance(paths, str): @@ -60,7 +59,7 @@ def create_external_data_share( raise FabricHTTPException(response) print( - f"{icons.green_dot} An external data share was created for the '{item_name}' {item_type} within the '{workspace}' workspace for the {paths} paths." + f"{icons.green_dot} An external data share was created for the '{item_name}' {item_type} within the '{workspace_name}' workspace for the {paths} paths." ) @@ -68,7 +67,7 @@ def revoke_external_data_share( external_data_share_id: UUID, item_name: str, item_type: str, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Revokes the specified external data share. Note: This action cannot be undone. @@ -83,15 +82,15 @@ def revoke_external_data_share( The item name. item_type : str The `item type `_. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=item_name, type=item_type, workspace=workspace + item_name=item_name, type=item_type, workspace=workspace_id ) client = fabric.FabricRestClient() @@ -103,12 +102,12 @@ def revoke_external_data_share( raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{external_data_share_id}' external data share for the '{item_name}' {item_type} within the '{workspace}' workspace has been revoked." + f"{icons.green_dot} The '{external_data_share_id}' external data share for the '{item_name}' {item_type} within the '{workspace_name}' workspace has been revoked." ) def list_external_data_shares_in_item( - item_name: str, item_type: str, workspace: Optional[str] = None + item_name: str, item_type: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Returns a list of the external data shares that exist for the specified item. @@ -121,8 +120,8 @@ def list_external_data_shares_in_item( The item name. item_type : str The `item type `_. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -132,9 +131,9 @@ def list_external_data_shares_in_item( A pandas dataframe showing a list of the external data shares that exist for the specified item. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=item_name, type=item_type, workspace=workspace + item_name=item_name, type=item_type, workspace=workspace_id ) client = fabric.FabricRestClient() diff --git a/src/sempy_labs/_generate_semantic_model.py b/src/sempy_labs/_generate_semantic_model.py index e1ecfa5..547df71 100644 --- a/src/sempy_labs/_generate_semantic_model.py +++ b/src/sempy_labs/_generate_semantic_model.py @@ -6,7 +6,7 @@ from sempy_labs._helper_functions import ( resolve_lakehouse_name, resolve_workspace_name_and_id, - resolve_dataset_id, + resolve_dataset_name_and_id, _conv_b64, _decode_b64, lro, @@ -118,7 +118,7 @@ def create_blank_semantic_model( def create_semantic_model_from_bim( - dataset: str, bim_file: dict, workspace: Optional[str] = None + dataset: str, bim_file: dict, workspace: Optional[str | UUID] = None ): """ Creates a new semantic model based on a Model.bim file. @@ -131,20 +131,20 @@ def create_semantic_model_from_bim( Name of the semantic model. bim_file : dict The model.bim file. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfI = fabric.list_datasets(workspace=workspace, mode="rest") + dfI = fabric.list_datasets(workspace=workspace_id, mode="rest") dfI_filt = dfI[(dfI["Dataset Name"] == dataset)] if len(dfI_filt) > 0: raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model already exists as a semantic model in the '{workspace}' workspace." + f"{icons.red_dot} The '{dataset}' semantic model already exists as a semantic model in the '{workspace_name}' workspace." ) client = fabric.FabricRestClient() @@ -179,12 +179,12 @@ def create_semantic_model_from_bim( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{dataset}' semantic model has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{dataset}' semantic model has been created within the '{workspace_name}' workspace." ) def update_semantic_model_from_bim( - dataset: str, bim_file: dict, workspace: Optional[str] = None + dataset: str | UUID, bim_file: dict, workspace: Optional[str | UUID] = None ): """ Updates a semantic model definition based on a Model.bim file. @@ -193,18 +193,18 @@ def update_semantic_model_from_bim( Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. bim_file : dict The model.bim file. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) client = fabric.FabricRestClient() defPBIDataset = {"version": "1.0", "settings": {}} @@ -213,7 +213,7 @@ def update_semantic_model_from_bim( payloadBim = _conv_b64(bim_file) request_body = { - "displayName": dataset, + "displayName": dataset_name, "definition": { "parts": [ { @@ -238,15 +238,15 @@ def update_semantic_model_from_bim( lro(client, response, status_codes=[200, 202], return_status_code=True) print( - f"{icons.green_dot} The '{dataset}' semantic model has been updated within the '{workspace}' workspace." + f"{icons.green_dot} The '{dataset_name}' semantic model has been updated within the '{workspace_name}' workspace." ) def deploy_semantic_model( source_dataset: str, - source_workspace: Optional[str] = None, + source_workspace: Optional[str | UUID] = None, target_dataset: Optional[str] = None, - target_workspace: Optional[str] = None, + target_workspace: Optional[str | UUID] = None, refresh_target_dataset: bool = True, overwrite: bool = False, ): @@ -257,14 +257,14 @@ def deploy_semantic_model( ---------- source_dataset : str Name of the semantic model to deploy. - source_workspace : str, default=None - The Fabric workspace name. + source_workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. target_dataset: str Name of the new semantic model to be created. - target_workspace : str, default=None - The Fabric workspace name in which the new semantic model will be deployed. + target_workspace : str | UUID, default=None + The Fabric workspace name or ID in which the new semantic model will be deployed. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. refresh_target_dataset : bool, default=True @@ -273,49 +273,52 @@ def deploy_semantic_model( If set to True, overwrites the existing semantic model in the workspace if it exists. """ - source_workspace = fabric.resolve_workspace_name(source_workspace) + (source_workspace_name, source_workspace_id) = resolve_workspace_name_and_id(source_workspace) if target_workspace is None: - target_workspace = source_workspace + target_workspace_name = source_workspace_name + target_workspace_id = fabric.resolve_workspace_id(target_workspace_name) + else: + (target_workspace_name, target_workspace_id) = resolve_workspace_name_and_id(target_workspace) if target_dataset is None: target_dataset = source_dataset - if target_dataset == source_dataset and target_workspace == source_workspace: + if target_dataset == source_dataset and target_workspace_name == source_workspace_name: raise ValueError( f"{icons.red_dot} The 'dataset' and 'new_dataset' parameters have the same value. And, the 'workspace' and 'new_dataset_workspace' " f"parameters have the same value. At least one of these must be different. Please update the parameters." ) - dfD = fabric.list_datasets(workspace=target_workspace, mode="rest") + dfD = fabric.list_datasets(workspace=target_workspace_id, mode="rest") dfD_filt = dfD[dfD["Dataset Name"] == target_dataset] if len(dfD_filt) > 0 and not overwrite: raise ValueError( - f"{icons.warning} The '{target_dataset}' semantic model already exists within the '{target_workspace}' workspace. The 'overwrite' parameter is set to False so the source semantic model was not deployed to the target destination." + f"{icons.warning} The '{target_dataset}' semantic model already exists within the '{target_workspace_name}' workspace. The 'overwrite' parameter is set to False so the source semantic model was not deployed to the target destination." ) - bim = get_semantic_model_bim(dataset=source_dataset, workspace=source_workspace) + bim = get_semantic_model_bim(dataset=source_dataset, workspace=source_workspace_id) # Create the semantic model if the model does not exist if dfD_filt.empty: create_semantic_model_from_bim( dataset=target_dataset, bim_file=bim, - workspace=target_workspace, + workspace=target_workspace_id, ) # Update the semantic model if the model exists else: update_semantic_model_from_bim( - dataset=target_dataset, bim_file=bim, workspace=target_workspace + dataset=target_dataset, bim_file=bim, workspace=target_workspace_id ) if refresh_target_dataset: - refresh_semantic_model(dataset=target_dataset, workspace=target_workspace) + refresh_semantic_model(dataset=target_dataset, workspace=target_workspace_id) def get_semantic_model_bim( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, save_to_file_name: Optional[str] = None, lakehouse_workspace: Optional[str] = None, ) -> dict: @@ -324,10 +327,10 @@ def get_semantic_model_bim( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name in which the semantic model resides. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. save_to_file_name : str, default=None @@ -343,8 +346,11 @@ def get_semantic_model_bim( The Model.bim file for the semantic model. """ + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + bimJson = get_semantic_model_definition( - dataset=dataset, workspace=workspace, format="TMSL", return_dataframe=False + dataset=dataset_id, workspace=workspace_id, format="TMSL", return_dataframe=False ) if save_to_file_name is not None: @@ -364,16 +370,16 @@ def get_semantic_model_bim( with open(filePath, "w") as json_file: json.dump(bimJson, json_file, indent=4) print( - f"{icons.green_dot} The {fileExt} file for the '{dataset}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n" + f"{icons.green_dot} The {fileExt} file for the '{dataset_name}' semantic model has been saved to the '{lakehouse}' in this location: '{filePath}'.\n\n" ) return bimJson def get_semantic_model_definition( - dataset: str, + dataset: str | UUID, format: str = "TMSL", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, return_dataframe: bool = True, ) -> pd.DataFrame | dict | List: """ @@ -383,12 +389,12 @@ def get_semantic_model_definition( Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. format : str, default="TMSL" The output format. Valid options are "TMSL" or "TMDL". "TMSL" returns the .bim file whereas "TMDL" returns the collection of TMDL files. Can also enter 'bim' for the TMSL version. - workspace : str, default=None - The Fabric workspace name in which the semantic model resides. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. return_dataframe : bool, default=True @@ -411,10 +417,10 @@ def get_semantic_model_definition( f"{icons.red_dot} Invalid format. Valid options: {valid_formats}." ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) client = fabric.FabricRestClient() - dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace) response = client.post( f"/v1/workspaces/{workspace_id}/semanticModels/{dataset_id}/getDefinition?format={format}", ) @@ -438,21 +444,39 @@ def get_semantic_model_definition( return decoded_parts -def get_semantic_model_size(dataset: str, workspace: Optional[str] = None): +def get_semantic_model_size(dataset: str | UUID, workspace: Optional[str | UUID] = None): + """ + Gets size of the semantic model in bytes. + + Parameters + ---------- + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. + Defaults to None which resolves to the workspace of the attached lakehouse + or if no lakehouse attached, resolves to the workspace of the notebook. + + Returns + ------- + int + The size of the semantic model in + """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) dict = fabric.evaluate_dax( - dataset=dataset, - workspace=workspace, + dataset=dataset_id, + workspace=workspace_id, dax_string=""" EVALUATE SELECTCOLUMNS(FILTER(INFO.STORAGETABLECOLUMNS(), [COLUMN_TYPE] = "BASIC_DATA"),[DICTIONARY_SIZE]) """, ) used_size = fabric.evaluate_dax( - dataset=dataset, - workspace=workspace, + dataset=dataset_id, + workspace=workspace_id, dax_string=""" EVALUATE SELECTCOLUMNS(INFO.STORAGETABLECOLUMNSEGMENTS(),[USED_SIZE]) """, diff --git a/src/sempy_labs/_git.py b/src/sempy_labs/_git.py index 4e58078..f45036d 100644 --- a/src/sempy_labs/_git.py +++ b/src/sempy_labs/_git.py @@ -7,6 +7,7 @@ lro, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def connect_workspace_to_git( @@ -16,7 +17,7 @@ def connect_workspace_to_git( branch_name: str, directory_name: str, git_provider_type: str = "AzureDevOps", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Connects a workspace to a git repository. @@ -37,13 +38,13 @@ def connect_workspace_to_git( The directory name. git_provider_type : str, default="AzureDevOps" A `Git provider type `_. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = { "gitProviderDetails": { @@ -64,11 +65,11 @@ def connect_workspace_to_git( raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{workspace}' workspace has been connected to the '{project_name}' Git project within the '{repository_name}' repository." + f"{icons.green_dot} The '{workspace_name}' workspace has been connected to the '{project_name}' Git project within the '{repository_name}' repository." ) -def disconnect_workspace_from_git(workspace: Optional[str] = None): +def disconnect_workspace_from_git(workspace: Optional[str | UUID] = None): """ Disconnects a workpsace from a git repository. @@ -76,13 +77,13 @@ def disconnect_workspace_from_git(workspace: Optional[str] = None): Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.post(f"/v1/workspaces/{workspace_id}/git/disconnect") @@ -90,11 +91,11 @@ def disconnect_workspace_from_git(workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{workspace}' workspace has been disconnected from Git." + f"{icons.green_dot} The '{workspace_name}' workspace has been disconnected from Git." ) -def get_git_status(workspace: Optional[str] = None) -> pd.DataFrame: +def get_git_status(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Obtains the Git status of items in the workspace, that can be committed to Git. @@ -102,8 +103,8 @@ def get_git_status(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -113,7 +114,7 @@ def get_git_status(workspace: Optional[str] = None) -> pd.DataFrame: A pandas dataframe showing the Git status of items in the workspace. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=[ @@ -157,7 +158,7 @@ def get_git_status(workspace: Optional[str] = None) -> pd.DataFrame: return df -def get_git_connection(workspace: Optional[str] = None) -> pd.DataFrame: +def get_git_connection(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Obtains the Git status of items in the workspace, that can be committed to Git. @@ -165,8 +166,8 @@ def get_git_connection(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -176,7 +177,7 @@ def get_git_connection(workspace: Optional[str] = None) -> pd.DataFrame: A pandas dataframe showing the Git status of items in the workspace. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=[ @@ -217,7 +218,7 @@ def get_git_connection(workspace: Optional[str] = None) -> pd.DataFrame: return df -def initialize_git_connection(workspace: Optional[str] = None) -> str: +def initialize_git_connection(workspace: Optional[str | UUID] = None) -> str: """ Initializes a connection for a workspace that is connected to Git. @@ -225,8 +226,8 @@ def initialize_git_connection(workspace: Optional[str] = None) -> str: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -236,7 +237,7 @@ def initialize_git_connection(workspace: Optional[str] = None) -> str: Remote full SHA commit hash. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.post(f"/v1/workspaces/{workspace_id}/git/initializeConnection") @@ -247,14 +248,14 @@ def initialize_git_connection(workspace: Optional[str] = None) -> str: lro(client, response) print( - f"{icons.green_dot} The '{workspace}' workspace git connection has been initialized." + f"{icons.green_dot} The '{workspace_name}' workspace git connection has been initialized." ) return response.json().get("remoteCommitHash") def commit_to_git( - comment: str, item_ids: str | List[str] = None, workspace: Optional[str] = None + comment: str, item_ids: str | List[str] = None, workspace: Optional[str | UUID] = None ): """ Commits all or a selection of items within a workspace to Git. @@ -268,15 +269,15 @@ def commit_to_git( item_ids : str | List[str], default=None A list of item Ids to commit to Git. Defaults to None which commits all items to Git. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - gs = get_git_status(workspace=workspace) + gs = get_git_status(workspace=workspace_id) if not gs.empty: workspace_head = gs["Workspace Head"].iloc[0] @@ -310,15 +311,15 @@ def commit_to_git( if commit_mode == "All": print( - f"{icons.green_dot} All items within the '{workspace}' workspace have been committed to Git." + f"{icons.green_dot} All items within the '{workspace_name}' workspace have been committed to Git." ) else: print( - f"{icons.green_dot} The {item_ids} items within the '{workspace}' workspace have been committed to Git." + f"{icons.green_dot} The {item_ids} items within the '{workspace_name}' workspace have been committed to Git." ) else: print( - f"{icons.info} Git already up to date: no modified items found within the '{workspace}' workspace." + f"{icons.info} Git already up to date: no modified items found within the '{workspace_name}' workspace." ) @@ -327,7 +328,7 @@ def update_from_git( conflict_resolution_policy: str, workspace_head: Optional[str] = None, allow_override: bool = False, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates the workspace with commits pushed to the connected branch. @@ -345,13 +346,13 @@ def update_from_git( In other cases, the system will validate that the given value is aligned with the head known to the system. allow_override : bool, default=False User consent to override incoming items during the update from Git process. When incoming items are present and the allow override items is not specified or is provided as false, the update operation will not start. Default value is false. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) conflict_resolution_policies = ["PreferWorkspace", "PreferRemote"] if "remote" in [policy.lower() for policy in conflict_resolution_policies]: @@ -388,5 +389,5 @@ def update_from_git( lro(client, response, return_status_code=True) print( - f"{icons.green_dot} The '{workspace}' workspace has been updated with commits pushed to the connected branch." + f"{icons.green_dot} The '{workspace_name}' workspace has been updated with commits pushed to the connected branch." ) diff --git a/src/sempy_labs/_helper_functions.py b/src/sempy_labs/_helper_functions.py index 5edfab6..b90a4f8 100644 --- a/src/sempy_labs/_helper_functions.py +++ b/src/sempy_labs/_helper_functions.py @@ -102,7 +102,7 @@ def create_relationship_name( ) -def resolve_report_id(report: str, workspace: Optional[str] = None) -> UUID: +def resolve_report_id(report: str, workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the ID of the Power BI report. @@ -110,8 +110,8 @@ def resolve_report_id(report: str, workspace: Optional[str] = None) -> UUID: ---------- report : str The name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -121,16 +121,10 @@ def resolve_report_id(report: str, workspace: Optional[str] = None) -> UUID: The ID of the Power BI report. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - - obj = fabric.resolve_item_id(item_name=report, type="Report", workspace=workspace) - - return obj + return fabric.resolve_item_id(item_name=report, type="Report", workspace=workspace) -def resolve_report_name(report_id: UUID, workspace: Optional[str] = None) -> str: +def resolve_report_name(report_id: UUID, workspace: Optional[str | UUID] = None) -> str: """ Obtains the name of the Power BI report. @@ -139,7 +133,7 @@ def resolve_report_name(report_id: UUID, workspace: Optional[str] = None) -> str report_id : UUID The name of the Power BI report. workspace : str, default=None - The Fabric workspace name. + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -149,16 +143,10 @@ def resolve_report_name(report_id: UUID, workspace: Optional[str] = None) -> str The name of the Power BI report. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - - obj = fabric.resolve_item_name( + return fabric.resolve_item_name( item_id=report_id, type="Report", workspace=workspace ) - return obj - def resolve_dataset_name_and_id( dataset: str | UUID, workspace: Optional[str] = None @@ -180,7 +168,7 @@ def resolve_dataset_name_and_id( return dataset_name, dataset_id -def resolve_dataset_id(dataset: str | UUID, workspace: Optional[str] = None) -> UUID: +def resolve_dataset_id(dataset: str | UUID, workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the ID of the semantic model. @@ -188,7 +176,7 @@ def resolve_dataset_id(dataset: str | UUID, workspace: Optional[str] = None) -> ---------- dataset : str | UUID The name or ID of the semantic model. - workspace : str, default=None + workspace : str | UUID, default=None The Fabric workspace name. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -209,7 +197,7 @@ def resolve_dataset_id(dataset: str | UUID, workspace: Optional[str] = None) -> return dataset_id -def resolve_dataset_name(dataset_id: UUID, workspace: Optional[str] = None) -> str: +def resolve_dataset_name(dataset_id: UUID, workspace: Optional[str | UUID] = None) -> str: """ Obtains the name of the semantic model. @@ -217,7 +205,7 @@ def resolve_dataset_name(dataset_id: UUID, workspace: Optional[str] = None) -> s ---------- dataset_id : UUID The name of the semantic model. - workspace : str, default=None + workspace : str | UUID, default=None The Fabric workspace name. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -228,19 +216,13 @@ def resolve_dataset_name(dataset_id: UUID, workspace: Optional[str] = None) -> s The name of the semantic model. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - - obj = fabric.resolve_item_name( + return fabric.resolve_item_name( item_id=dataset_id, type="SemanticModel", workspace=workspace ) - return obj - def resolve_lakehouse_name( - lakehouse_id: Optional[UUID] = None, workspace: Optional[str] = None + lakehouse_id: Optional[UUID] = None, workspace: Optional[str | UUID] = None ) -> str: """ Obtains the name of the Fabric lakehouse. @@ -250,8 +232,8 @@ def resolve_lakehouse_name( lakehouse_id : UUID, default=None The name of the Fabric lakehouse. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -261,21 +243,15 @@ def resolve_lakehouse_name( The name of the Fabric lakehouse. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - if lakehouse_id is None: lakehouse_id = fabric.get_lakehouse_id() - obj = fabric.resolve_item_name( + return fabric.resolve_item_name( item_id=lakehouse_id, type="Lakehouse", workspace=workspace ) - return obj - -def resolve_lakehouse_id(lakehouse: str, workspace: Optional[str] = None) -> UUID: +def resolve_lakehouse_id(lakehouse: str, workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the ID of the Fabric lakehouse. @@ -283,8 +259,8 @@ def resolve_lakehouse_id(lakehouse: str, workspace: Optional[str] = None) -> UUI ---------- lakehouse : str The name of the Fabric lakehouse. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -294,16 +270,10 @@ def resolve_lakehouse_id(lakehouse: str, workspace: Optional[str] = None) -> UUI The ID of the Fabric lakehouse. """ - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - - obj = fabric.resolve_item_id( + return fabric.resolve_item_id( item_name=lakehouse, type="Lakehouse", workspace=workspace ) - return obj - def get_direct_lake_sql_endpoint(dataset: str, workspace: Optional[str] = None) -> UUID: """ @@ -326,9 +296,8 @@ def get_direct_lake_sql_endpoint(dataset: str, workspace: Optional[str] = None) from sempy_labs.tom import connect_semantic_model - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) # dfP = fabric.list_partitions(dataset=dataset, workspace=workspace) # dfP_filt = dfP[dfP["Mode"] == "DirectLake"] @@ -339,7 +308,7 @@ def get_direct_lake_sql_endpoint(dataset: str, workspace: Optional[str] = None) # ) with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: sqlEndpointId = None for e in tom.model.Expressions: @@ -349,7 +318,7 @@ def get_direct_lake_sql_endpoint(dataset: str, workspace: Optional[str] = None) sqlEndpointId = matches[1] if sqlEndpointId is None: - raise ValueError("SQL Endpoint not found.") + raise ValueError(f"{icons.red_dot} SQL Endpoint not found.") return sqlEndpointId @@ -426,7 +395,7 @@ def save_as_delta_table( merge_schema: bool = False, schema: Optional[dict] = None, lakehouse: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Saves a pandas dataframe as a delta table in a Fabric lakehouse. @@ -446,8 +415,8 @@ def save_as_delta_table( lakehouse : str, default=None The Fabric lakehouse used by the Direct Lake semantic model. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -466,19 +435,15 @@ def save_as_delta_table( TimestampType, ) - if workspace is None: - workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) - else: - workspace_id = fabric.resolve_workspace_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() lakehouse = resolve_lakehouse_name( - lakehouse_id=lakehouse_id, workspace=workspace + lakehouse_id=lakehouse_id, workspace=workspace_id ) else: - lakehouse_id = resolve_lakehouse_id(lakehouse, workspace) + lakehouse_id = resolve_lakehouse_id(lakehouse, workspace_id) writeModes = ["append", "overwrite"] write_mode = write_mode.lower() @@ -534,7 +499,7 @@ def save_as_delta_table( else: spark_df.write.mode(write_mode).format("delta").save(filePath) print( - f"{icons.green_dot} The dataframe has been saved as the '{delta_table_name}' table in the '{lakehouse}' lakehouse within the '{workspace}' workspace." + f"{icons.green_dot} The dataframe has been saved as the '{delta_table_name}' table in the '{lakehouse}' lakehouse within the '{workspace_name}' workspace." ) @@ -574,14 +539,14 @@ def language_validate(language: str): return lang -def resolve_workspace_name_and_id(workspace: Optional[str] = None) -> Tuple[str, str]: +def resolve_workspace_name_and_id(workspace: Optional[str | UUID] = None) -> Tuple[str, str]: """ Obtains the name and ID of the Fabric workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -593,11 +558,12 @@ def resolve_workspace_name_and_id(workspace: Optional[str] = None) -> Tuple[str, if workspace is None: workspace_id = fabric.get_workspace_id() - workspace = fabric.resolve_workspace_name(workspace_id) + workspace_name = fabric.resolve_workspace_name(workspace_id) else: workspace_id = fabric.resolve_workspace_id(workspace) + workspace_name = workspace - return str(workspace), str(workspace_id) + return str(workspace_name), str(workspace_id) def _extract_json(dataframe: pd.DataFrame) -> dict: @@ -623,7 +589,7 @@ def _decode_b64(file, format: Optional[str] = "utf-8"): return result -def is_default_semantic_model(dataset: str, workspace: Optional[str] = None) -> bool: +def is_default_semantic_model(dataset: str, workspace: Optional[str | UUID] = None) -> bool: """ Identifies whether a semantic model is a default semantic model. @@ -631,8 +597,8 @@ def is_default_semantic_model(dataset: str, workspace: Optional[str] = None) -> ---------- dataset : str The name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -642,9 +608,9 @@ def is_default_semantic_model(dataset: str, workspace: Optional[str] = None) -> A True/False value indicating whether the semantic model is a default semantic model. """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfI = fabric.list_items(workspace=workspace) + dfI = fabric.list_items(workspace=workspace_id) filtered_df = dfI.groupby("Display Name").filter( lambda x: set(["Warehouse", "SemanticModel"]).issubset(set(x["Type"])) or set(["Lakehouse", "SemanticModel"]).issubset(set(x["Type"])) @@ -654,7 +620,7 @@ def is_default_semantic_model(dataset: str, workspace: Optional[str] = None) -> return dataset in default_semantic_models -def resolve_item_type(item_id: UUID, workspace: Optional[str] = None) -> str: +def resolve_item_type(item_id: UUID, workspace: Optional[str | UUID] = None) -> str: """ Obtains the item type for a given Fabric Item Id within a Fabric workspace. @@ -662,8 +628,8 @@ def resolve_item_type(item_id: UUID, workspace: Optional[str] = None) -> str: ---------- item_id : UUID The item/artifact Id. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -673,21 +639,19 @@ def resolve_item_type(item_id: UUID, workspace: Optional[str] = None) -> str: The item type for the item Id. """ - workspace = fabric.resolve_workspace_name(workspace) - dfI = fabric.list_items(workspace=workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + dfI = fabric.list_items(workspace=workspace_id) dfI_filt = dfI[dfI["Id"] == item_id] - if len(dfI_filt) == 0: + if dfI_filt.empty: raise ValueError( - f"Invalid 'item_id' parameter. The '{item_id}' item was not found in the '{workspace}' workspace." + f"Invalid 'item_id' parameter. The '{item_id}' item was not found in the '{workspace_name}' workspace." ) - item_type = dfI_filt["Type"].iloc[0] - - return item_type + return dfI_filt["Type"].iloc[0] def resolve_dataset_from_report( - report: str, workspace: Optional[str] = None + report: str, workspace: Optional[str | UUID] = None ) -> Tuple[UUID, str, UUID, str]: """ Obtains the basic semantic model properties from which the report's data is sourced. @@ -696,8 +660,8 @@ def resolve_dataset_from_report( ---------- report : str The name of the Power BI report. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -707,13 +671,13 @@ def resolve_dataset_from_report( The semantic model UUID, semantic model name, semantic model workspace UUID, semantic model workspace name """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfR = fabric.list_reports(workspace=workspace) + dfR = fabric.list_reports(workspace=workspace_id) dfR_filt = dfR[dfR["Name"] == report] if len(dfR_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{report}' report does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{report}' report does not exist within the '{workspace_name}' workspace." ) dataset_id = dfR_filt["Dataset Id"].iloc[0] dataset_workspace_id = dfR_filt["Dataset Workspace Id"].iloc[0] @@ -763,14 +727,14 @@ def resolve_workspace_capacity(workspace: Optional[str | UUID] = None) -> Tuple[ return capacity_id, capacity_name -def get_capacity_id(workspace: Optional[str] = None) -> UUID: +def get_capacity_id(workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the Capacity Id for a given workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -781,28 +745,27 @@ def get_capacity_id(workspace: Optional[str] = None) -> UUID: """ if workspace is None: - capacity_id = _get_x_id(name="trident.capacity.id") + capacity_id = _get_fabric_context_setting(name="trident.capacity.id") else: - - workspace = fabric.resolve_workspace_name(workspace) - filter_condition = urllib.parse.quote(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + filter_condition = urllib.parse.quote(workspace_name) dfW = fabric.list_workspaces(filter=f"name eq '{filter_condition}'") if len(dfW) == 0: - raise ValueError(f"{icons.red_dot} The '{workspace}' does not exist'.") + raise ValueError(f"{icons.red_dot} The '{workspace_name}' does not exist'.") capacity_id = dfW["Capacity Id"].iloc[0] return capacity_id -def get_capacity_name(workspace: Optional[str] = None) -> str: +def get_capacity_name(workspace: Optional[str | UUID] = None) -> str: """ Obtains the capacity name for a given workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -1020,7 +983,7 @@ def _get_adls_client(account_name): return service_client -def resolve_warehouse_id(warehouse: str, workspace: Optional[str]) -> UUID: +def resolve_warehouse_id(warehouse: str, workspace: Optional[str | UUID]) -> UUID: """ Obtains the Id for a given warehouse. @@ -1028,6 +991,10 @@ def resolve_warehouse_id(warehouse: str, workspace: Optional[str]) -> UUID: ---------- warehouse : str The warehouse name + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. + Defaults to None which resolves to the workspace of the attached lakehouse + or if no lakehouse attached, resolves to the workspace of the notebook. Returns ------- @@ -1035,7 +1002,6 @@ def resolve_warehouse_id(warehouse: str, workspace: Optional[str]) -> UUID: The warehouse Id. """ - workspace = fabric.resolve_workspace_name(workspace) return fabric.resolve_item_id( item_name=warehouse, type="Warehouse", workspace=workspace ) @@ -1097,7 +1063,7 @@ def convert_to_alphanumeric_lowercase(input_string): return cleaned_string -def resolve_environment_id(environment: str, workspace: Optional[str] = None) -> UUID: +def resolve_environment_id(environment: str, workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the environment Id for a given environment. @@ -1105,6 +1071,10 @@ def resolve_environment_id(environment: str, workspace: Optional[str] = None) -> ---------- environment: str Name of the environment. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. + Defaults to None which resolves to the workspace of the attached lakehouse + or if no lakehouse attached, resolves to the workspace of the notebook. Returns ------- @@ -1112,7 +1082,6 @@ def resolve_environment_id(environment: str, workspace: Optional[str] = None) -> The environment Id. """ - workspace = fabric.resolve_workspace_name(workspace) return fabric.resolve_item_id( item_name=environment, type="Environment", workspace=workspace ) @@ -1147,7 +1116,7 @@ def convert_to_friendly_case(text: str) -> str: return text -def resolve_notebook_id(notebook: str, workspace: Optional[str] = None) -> UUID: +def resolve_notebook_id(notebook: str, workspace: Optional[str | UUID] = None) -> UUID: """ Obtains the notebook Id for a given notebook. @@ -1155,6 +1124,10 @@ def resolve_notebook_id(notebook: str, workspace: Optional[str] = None) -> UUID: ---------- notebook: str Name of the notebook. + workspace : str | UUID, default=None + The Fabric workspace name or ID in which the semantic model resides. + Defaults to None which resolves to the workspace of the attached lakehouse + or if no lakehouse attached, resolves to the workspace of the notebook. Returns ------- @@ -1162,7 +1135,6 @@ def resolve_notebook_id(notebook: str, workspace: Optional[str] = None) -> UUID: The notebook Id. """ - workspace = fabric.resolve_workspace_name(workspace) return fabric.resolve_item_id( item_name=notebook, type="Notebook", workspace=workspace ) @@ -1190,7 +1162,7 @@ def _make_list_unique(my_list): return list(set(my_list)) -def _get_partition_map(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: +def _get_partition_map(dataset: str, workspace: Optional[str | UUID] = None) -> pd.DataFrame: (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) diff --git a/src/sempy_labs/_kql_databases.py b/src/sempy_labs/_kql_databases.py index 3999b50..4a5ce05 100644 --- a/src/sempy_labs/_kql_databases.py +++ b/src/sempy_labs/_kql_databases.py @@ -8,9 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_kql_databases(workspace: Optional[str] = None) -> pd.DataFrame: +def list_kql_databases(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the KQL databases within a workspace. @@ -18,8 +19,8 @@ def list_kql_databases(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -41,7 +42,7 @@ def list_kql_databases(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/kqlDatabases") @@ -69,7 +70,7 @@ def list_kql_databases(workspace: Optional[str] = None) -> pd.DataFrame: def create_kql_database( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a KQL database. @@ -82,13 +83,13 @@ def create_kql_database( Name of the KQL database. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -103,11 +104,11 @@ def create_kql_database( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' KQL database has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' KQL database has been created within the '{workspace_name}' workspace." ) -def delete_kql_database(name: str, workspace: Optional[str] = None): +def delete_kql_database(name: str, workspace: Optional[str | UUID] = None): """ Deletes a KQL database. @@ -117,15 +118,15 @@ def delete_kql_database(name: str, workspace: Optional[str] = None): ---------- name: str Name of the KQL database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) kql_database_id = fabric.resolve_item_id( - item_name=name, type="KQLDatabase", workspace=workspace + item_name=name, type="KQLDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -136,5 +137,5 @@ def delete_kql_database(name: str, workspace: Optional[str] = None): if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' KQL database within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' KQL database within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_kql_querysets.py b/src/sempy_labs/_kql_querysets.py index caccad1..b364316 100644 --- a/src/sempy_labs/_kql_querysets.py +++ b/src/sempy_labs/_kql_querysets.py @@ -8,9 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_kql_querysets(workspace: Optional[str] = None) -> pd.DataFrame: +def list_kql_querysets(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the KQL querysets within a workspace. @@ -18,8 +19,8 @@ def list_kql_querysets(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -37,7 +38,7 @@ def list_kql_querysets(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/kqlQuerysets") @@ -59,7 +60,7 @@ def list_kql_querysets(workspace: Optional[str] = None) -> pd.DataFrame: def create_kql_queryset( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a KQL queryset. @@ -72,13 +73,13 @@ def create_kql_queryset( Name of the KQL queryset. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -93,11 +94,11 @@ def create_kql_queryset( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' KQL queryset has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' KQL queryset has been created within the '{workspace_name}' workspace." ) -def delete_kql_queryset(name: str, workspace: Optional[str] = None): +def delete_kql_queryset(name: str, workspace: Optional[str | UUID] = None): """ Deletes a KQL queryset. @@ -107,15 +108,15 @@ def delete_kql_queryset(name: str, workspace: Optional[str] = None): ---------- name: str Name of the KQL queryset. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) kql_database_id = fabric.resolve_item_id( - item_name=name, type="KQLQueryset", workspace=workspace + item_name=name, type="KQLQueryset", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -126,5 +127,5 @@ def delete_kql_queryset(name: str, workspace: Optional[str] = None): if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' KQL queryset within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' KQL queryset within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_list_functions.py b/src/sempy_labs/_list_functions.py index 60103a3..a90445e 100644 --- a/src/sempy_labs/_list_functions.py +++ b/src/sempy_labs/_list_functions.py @@ -17,7 +17,7 @@ def get_object_level_security( - dataset: str | UUID, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows the object level security for the semantic model. @@ -26,8 +26,8 @@ def get_object_level_security( ---------- dataset : str | UUID Name or ID of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -85,7 +85,7 @@ def get_object_level_security( def list_tables( - dataset: str | UUID, workspace: Optional[str] = None, extended: bool = False + dataset: str | UUID, workspace: Optional[str | UUID] = None, extended: bool = False ) -> pd.DataFrame: """ Shows a semantic model's tables and their properties. @@ -94,8 +94,8 @@ def list_tables( ---------- dataset : str | UUID Name or ID of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. extended : bool, default=False @@ -258,16 +258,16 @@ def list_tables( return df -def list_annotations(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: +def list_annotations(dataset: str | UUID, workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows a semantic model's annotations and their properties. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -279,7 +279,8 @@ def list_annotations(dataset: str, workspace: Optional[str] = None) -> pd.DataFr from sempy_labs.tom import connect_semantic_model - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) df = pd.DataFrame( columns=[ @@ -292,7 +293,7 @@ def list_annotations(dataset: str, workspace: Optional[str] = None) -> pd.DataFr ) with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: mName = tom.model.Name @@ -489,8 +490,8 @@ def list_annotations(dataset: str, workspace: Optional[str] = None) -> pd.DataFr def list_columns( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, lakehouse: Optional[str] = None, lakehouse_workspace: Optional[str] = None, ) -> pd.DataFrame: @@ -499,10 +500,10 @@ def list_columns( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. lakehouse : str, default=None @@ -523,20 +524,21 @@ def list_columns( ) from pyspark.sql import SparkSession - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfP = fabric.list_partitions(dataset=dataset, workspace=workspace) + dfP = fabric.list_partitions(dataset=dataset_id, workspace=workspace_id) isDirectLake = any(r["Mode"] == "DirectLake" for i, r in dfP.iterrows()) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id) if isDirectLake: dfC["Column Cardinality"] = None sql_statements = [] (lakeID, lakeName) = get_direct_lake_lakehouse( - dataset=dataset, - workspace=workspace, + dataset=dataset_id, + workspace=workspace_id, lakehouse=lakehouse, lakehouse_workspace=lakehouse_workspace, ) @@ -590,14 +592,14 @@ def list_columns( return dfC -def list_dashboards(workspace: Optional[str] = None) -> pd.DataFrame: +def list_dashboards(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows a list of the dashboards within a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -620,7 +622,7 @@ def list_dashboards(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.PowerBIRestClient() response = client.get(f"/v1.0/myorg/groups/{workspace_id}/dashboards") @@ -645,14 +647,14 @@ def list_dashboards(workspace: Optional[str] = None) -> pd.DataFrame: return df -def list_lakehouses(workspace: Optional[str] = None) -> pd.DataFrame: +def list_lakehouses(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the lakehouses within a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -675,7 +677,7 @@ def list_lakehouses(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/lakehouses") @@ -705,14 +707,14 @@ def list_lakehouses(workspace: Optional[str] = None) -> pd.DataFrame: return df -def list_sql_endpoints(workspace: Optional[str] = None) -> pd.DataFrame: +def list_sql_endpoints(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the SQL endpoints within a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -724,7 +726,7 @@ def list_sql_endpoints(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["SQL Endpoint Id", "SQL Endpoint Name", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/sqlEndpoints") @@ -746,14 +748,14 @@ def list_sql_endpoints(workspace: Optional[str] = None) -> pd.DataFrame: return df -def list_datamarts(workspace: Optional[str] = None) -> pd.DataFrame: +def list_datamarts(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the datamarts within a workspace. Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -765,7 +767,7 @@ def list_datamarts(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["Datamart Name", "Datamart ID", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/datamarts") @@ -791,7 +793,7 @@ def update_item( current_name: str, new_name: str, description: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates the name/description of a Fabric item. @@ -806,13 +808,13 @@ def update_item( The new name of the item. description : str, default=None A description of the item. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_type = item_type.replace(" ", "").capitalize() if item_type not in icons.itemTypes.keys(): @@ -822,12 +824,12 @@ def update_item( itemType = icons.itemTypes[item_type] - dfI = fabric.list_items(workspace=workspace, type=item_type) + dfI = fabric.list_items(workspace=workspace_id, type=item_type) dfI_filt = dfI[(dfI["Display Name"] == current_name)] if len(dfI_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{current_name}' {item_type} does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{current_name}' {item_type} does not exist within the '{workspace_name}' workspace." ) itemId = dfI_filt["Id"].iloc[0] @@ -845,16 +847,16 @@ def update_item( raise FabricHTTPException(response) if description is None: print( - f"{icons.green_dot} The '{current_name}' {item_type} within the '{workspace}' workspace has been updated to be named '{new_name}'" + f"{icons.green_dot} The '{current_name}' {item_type} within the '{workspace_name}' workspace has been updated to be named '{new_name}'" ) else: print( - f"{icons.green_dot} The '{current_name}' {item_type} within the '{workspace}' workspace has been updated to be named '{new_name}' and have a description of '{description}'" + f"{icons.green_dot} The '{current_name}' {item_type} within the '{workspace_name}' workspace has been updated to be named '{new_name}' and have a description of '{description}'" ) def list_relationships( - dataset: str | UUID, workspace: Optional[str] = None, extended: bool = False + dataset: str | UUID, workspace: Optional[str | UUID] = None, extended: bool = False ) -> pd.DataFrame: """ Shows a semantic model's relationships and their properties. @@ -863,8 +865,8 @@ def list_relationships( ---------- dataset: str | UUID Name or UUID of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. extended : bool, default=False @@ -937,16 +939,16 @@ def parse_value(text): return dfR -def list_kpis(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: +def list_kpis(dataset: str | UUID, workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows a semantic model's KPIs and their properties. Parameters ---------- - dataset: str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset: str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -958,8 +960,11 @@ def list_kpis(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: from sempy_labs.tom import connect_semantic_model + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + with connect_semantic_model( - dataset=dataset, workspace=workspace, readonly=True + dataset=dataset_id, workspace=workspace_id, readonly=True ) as tom: df = pd.DataFrame( @@ -1002,17 +1007,17 @@ def list_kpis(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: def list_semantic_model_objects( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of semantic model objects. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -1166,7 +1171,7 @@ def list_semantic_model_objects( def list_shortcuts( - lakehouse: Optional[str] = None, workspace: Optional[str] = None + lakehouse: Optional[str] = None, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows all shortcuts which exist in a Fabric lakehouse and their properties. @@ -1176,8 +1181,8 @@ def list_shortcuts( lakehouse : str, default=None The Fabric lakehouse name. Defaults to None which resolves to the lakehouse attached to the notebook. - workspace : str, default=None - The name of the Fabric workspace in which lakehouse resides. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace in which lakehouse resides. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -1187,12 +1192,12 @@ def list_shortcuts( A pandas dataframe showing all the shortcuts which exist in the specified lakehouse. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if lakehouse is None: lakehouse_id = fabric.get_lakehouse_id() else: - lakehouse_id = resolve_lakehouse_id(lakehouse, workspace) + lakehouse_id = resolve_lakehouse_id(lakehouse, workspace_id) client = fabric.FabricRestClient() @@ -1309,17 +1314,17 @@ def list_capacities() -> pd.DataFrame: def list_reports_using_semantic_model( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of all the reports (in all workspaces) which use a given semantic model. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -1338,8 +1343,9 @@ def list_reports_using_semantic_model( ] ) - workspace = fabric.resolve_workspace_name(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + client = fabric.PowerBIRestClient() response = client.get( f"metadata/relations/downstream/dataset/{dataset_id}?apiVersion=3" @@ -1366,7 +1372,7 @@ def list_reports_using_semantic_model( def list_report_semantic_model_objects( - dataset: str, workspace: Optional[str] = None, extended: bool = False + dataset: str | UUID, workspace: Optional[str | UUID] = None, extended: bool = False ) -> pd.DataFrame: """ Shows a list of semantic model objects (i.e. columns, measures, hierarchies) used in all reports which feed data from @@ -1376,10 +1382,10 @@ def list_report_semantic_model_objects( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. extended: bool, default=False @@ -1408,8 +1414,11 @@ def list_report_semantic_model_objects( ] ) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) + # Collect all reports which use the semantic model - dfR = list_reports_using_semantic_model(dataset=dataset, workspace=workspace) + dfR = list_reports_using_semantic_model(dataset=dataset_id, workspace=workspace_id) if len(dfR) == 0: return dfRO @@ -1433,7 +1442,7 @@ def list_report_semantic_model_objects( # Collect all semantic model objects if extended: with connect_semantic_model( - dataset=dataset, readonly=True, workspace=workspace + dataset=dataset_id, readonly=True, workspace=workspace_id ) as tom: for index, row in dfRO.iterrows(): object_type = row["Object Type"] @@ -1458,8 +1467,8 @@ def list_report_semantic_model_objects( def list_semantic_model_object_report_usage( - dataset: str, - workspace: Optional[str] = None, + dataset: str | UUID, + workspace: Optional[str | UUID] = None, include_dependencies: bool = False, extended: bool = False, ) -> pd.DataFrame: @@ -1470,10 +1479,10 @@ def list_semantic_model_object_report_usage( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. include_dependencies : bool, default=False @@ -1491,9 +1500,10 @@ def list_semantic_model_object_report_usage( from sempy_labs._model_dependencies import get_model_calc_dependencies from sempy_labs._helper_functions import format_dax_object_name - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfR = list_report_semantic_model_objects(dataset=dataset, workspace=workspace) + dfR = list_report_semantic_model_objects(dataset=dataset_id, workspace=workspace_id) usage_column_name = "Report Usage Count" if not include_dependencies: @@ -1504,7 +1514,7 @@ def list_semantic_model_object_report_usage( ) else: df = pd.DataFrame(columns=["Table Name", "Object Name", "Object Type"]) - dep = get_model_calc_dependencies(dataset=dataset, workspace=workspace) + dep = get_model_calc_dependencies(dataset=dataset_id, workspace=workspace_id) for i, r in dfR.iterrows(): object_type = r["Object Type"] @@ -1544,7 +1554,7 @@ def list_semantic_model_object_report_usage( final_df["Object"] = format_dax_object_name( final_df["Table Name"], final_df["Object Name"] ) - dfC = fabric.list_columns(dataset=dataset, workspace=workspace, extended=True) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id, extended=True) dfC["Object"] = format_dax_object_name(dfC["Table Name"], dfC["Column Name"]) final_df = pd.merge( final_df, diff --git a/src/sempy_labs/_managed_private_endpoints.py b/src/sempy_labs/_managed_private_endpoints.py index f42bd80..bfc24e0 100644 --- a/src/sempy_labs/_managed_private_endpoints.py +++ b/src/sempy_labs/_managed_private_endpoints.py @@ -16,7 +16,7 @@ def create_managed_private_endpoint( target_private_link_resource_id: UUID, target_subresource_type: str, request_message: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates a managed private endpoint. @@ -33,13 +33,13 @@ def create_managed_private_endpoint( Sub-resource pointing to Private-link resoure. request_message : str, default=None Message to approve private endpoint request. Should not be more than 140 characters. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = { "name": name, @@ -62,11 +62,11 @@ def create_managed_private_endpoint( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' managed private endpoint has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' managed private endpoint has been created within the '{workspace_name}' workspace." ) -def list_managed_private_endpoints(workspace: Optional[str] = None) -> pd.DataFrame: +def list_managed_private_endpoints(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the managed private endpoints within a workspace. @@ -74,8 +74,8 @@ def list_managed_private_endpoints(workspace: Optional[str] = None) -> pd.DataFr Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -97,7 +97,7 @@ def list_managed_private_endpoints(workspace: Optional[str] = None) -> pd.DataFr ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/managedPrivateEndpoints") @@ -124,7 +124,7 @@ def list_managed_private_endpoints(workspace: Optional[str] = None) -> pd.DataFr def delete_managed_private_endpoint( - managed_private_endpoint: str, workspace: Optional[str] = None + managed_private_endpoint: str, workspace: Optional[str | UUID] = None ): """ Deletes a Fabric managed private endpoint. @@ -135,20 +135,20 @@ def delete_managed_private_endpoint( ---------- managed_private_endpoint: str Name of the managed private endpoint. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - df = list_managed_private_endpoints(workspace=workspace) + df = list_managed_private_endpoints(workspace=workspace_id) df_filt = df[df["Managed Private Endpoint Name"] == managed_private_endpoint] if len(df_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{managed_private_endpoint}' managed private endpoint does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{managed_private_endpoint}' managed private endpoint does not exist within the '{workspace_name}' workspace." ) item_id = df_filt["Managed Private Endpoint Id"].iloc[0] @@ -162,5 +162,5 @@ def delete_managed_private_endpoint( raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{managed_private_endpoint}' managed private endpoint within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{managed_private_endpoint}' managed private endpoint within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_mirrored_databases.py b/src/sempy_labs/_mirrored_databases.py index 7e27182..a095795 100644 --- a/src/sempy_labs/_mirrored_databases.py +++ b/src/sempy_labs/_mirrored_databases.py @@ -10,9 +10,10 @@ from sempy.fabric.exceptions import FabricHTTPException import sempy_labs._icons as icons import base64 +from uuid import UUID -def list_mirrored_databases(workspace: Optional[str] = None) -> pd.DataFrame: +def list_mirrored_databases(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the mirrored databases within a workspace. @@ -20,8 +21,8 @@ def list_mirrored_databases(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -44,7 +45,7 @@ def list_mirrored_databases(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/mirroredDatabases") @@ -72,7 +73,7 @@ def list_mirrored_databases(workspace: Optional[str] = None) -> pd.DataFrame: def create_mirrored_database( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric mirrored database. @@ -85,13 +86,13 @@ def create_mirrored_database( Name of the mirrored database. description : str, default=None A description of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -107,11 +108,11 @@ def create_mirrored_database( raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' mirrored database has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' mirrored database has been created within the '{workspace_name}' workspace." ) -def delete_mirrored_database(mirrored_database: str, workspace: Optional[str] = None): +def delete_mirrored_database(mirrored_database: str, workspace: Optional[str | UUID] = None): """ Deletes a mirrored database. @@ -121,16 +122,16 @@ def delete_mirrored_database(mirrored_database: str, workspace: Optional[str] = ---------- mirrored_database: str Name of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -142,12 +143,12 @@ def delete_mirrored_database(mirrored_database: str, workspace: Optional[str] = raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{mirrored_database}' mirrored database within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{mirrored_database}' mirrored database within the '{workspace_name}' workspace has been deleted." ) def get_mirroring_status( - mirrored_database: str, workspace: Optional[str] = None + mirrored_database: str, workspace: Optional[str | UUID] = None ) -> str: """ Get the status of the mirrored database. @@ -158,8 +159,8 @@ def get_mirroring_status( ---------- mirrored_database: str Name of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -169,10 +170,10 @@ def get_mirroring_status( The status of a mirrored database. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -187,7 +188,7 @@ def get_mirroring_status( def get_tables_mirroring_status( - mirrored_database: str, workspace: Optional[str] = None + mirrored_database: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Gets the mirroring status of the tables. @@ -198,8 +199,8 @@ def get_tables_mirroring_status( ---------- mirrored_database: str Name of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -209,10 +210,10 @@ def get_tables_mirroring_status( A pandas dataframe showing the mirroring status of the tables. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -257,7 +258,7 @@ def get_tables_mirroring_status( return df -def start_mirroring(mirrored_database: str, workspace: Optional[str] = None): +def start_mirroring(mirrored_database: str, workspace: Optional[str | UUID] = None): """ Starts the mirroring for a database. @@ -267,16 +268,16 @@ def start_mirroring(mirrored_database: str, workspace: Optional[str] = None): ---------- mirrored_database: str Name of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -288,11 +289,11 @@ def start_mirroring(mirrored_database: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} Mirroring has started for the '{mirrored_database}' database within the '{workspace}' workspace." + f"{icons.green_dot} Mirroring has started for the '{mirrored_database}' database within the '{workspace_name}' workspace." ) -def stop_mirroring(mirrored_database: str, workspace: Optional[str] = None): +def stop_mirroring(mirrored_database: str, workspace: Optional[str | UUID] = None): """ Stops the mirroring for a database. @@ -302,16 +303,16 @@ def stop_mirroring(mirrored_database: str, workspace: Optional[str] = None): ---------- mirrored_database: str Name of the mirrored database. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -323,12 +324,12 @@ def stop_mirroring(mirrored_database: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} Mirroring has stopped for the '{mirrored_database}' database within the '{workspace}' workspace." + f"{icons.green_dot} Mirroring has stopped for the '{mirrored_database}' database within the '{workspace_name}' workspace." ) def get_mirrored_database_definition( - mirrored_database: str, workspace: Optional[str] = None, decode: bool = True + mirrored_database: str, workspace: Optional[str | UUID] = None, decode: bool = True ) -> str: """ Obtains the mirrored database definition. @@ -339,8 +340,8 @@ def get_mirrored_database_definition( ---------- mirrored_database : str The name of the mirrored database. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. decode : bool, default=True @@ -353,9 +354,9 @@ def get_mirrored_database_definition( The mirrored database definition. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) client = fabric.FabricRestClient() response = client.post( @@ -378,7 +379,7 @@ def get_mirrored_database_definition( def update_mirrored_database_definition( mirrored_database: str, mirrored_database_content: dict, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates an existing notebook with a new definition. @@ -389,17 +390,17 @@ def update_mirrored_database_definition( The name of the mirrored database to be created. mirrored_database_content : dict The mirrored database definition (not in Base64 format). - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() payload = base64.b64encode(mirrored_database_content) item_id = fabric.resolve_item_id( - item_name=mirrored_database, type="MirroredDatabase", workspace=workspace + item_name=mirrored_database, type="MirroredDatabase", workspace=workspace_id ) request_body = { @@ -424,5 +425,5 @@ def update_mirrored_database_definition( lro(client, response, return_status_code=True) print( - f"{icons.green_dot} The '{mirrored_database}' mirrored database was updated within the '{workspace}' workspace." + f"{icons.green_dot} The '{mirrored_database}' mirrored database was updated within the '{workspace_name}' workspace." ) diff --git a/src/sempy_labs/_mirrored_warehouses.py b/src/sempy_labs/_mirrored_warehouses.py index cd6f97f..f1d6de8 100644 --- a/src/sempy_labs/_mirrored_warehouses.py +++ b/src/sempy_labs/_mirrored_warehouses.py @@ -6,9 +6,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_mirrored_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: +def list_mirrored_warehouses(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the mirrored warehouses within a workspace. @@ -16,8 +17,8 @@ def list_mirrored_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -31,7 +32,7 @@ def list_mirrored_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: columns=["Mirrored Warehouse Name", "Mirrored Warehouse Id", "Description"] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/mirroredWarehouses") diff --git a/src/sempy_labs/_ml_experiments.py b/src/sempy_labs/_ml_experiments.py index fee5fc5..018b94f 100644 --- a/src/sempy_labs/_ml_experiments.py +++ b/src/sempy_labs/_ml_experiments.py @@ -8,9 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_ml_experiments(workspace: Optional[str] = None) -> pd.DataFrame: +def list_ml_experiments(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the ML experiments within a workspace. @@ -18,8 +19,8 @@ def list_ml_experiments(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -31,7 +32,7 @@ def list_ml_experiments(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["ML Experiment Name", "ML Experiment Id", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/mlExperiments") @@ -57,7 +58,7 @@ def list_ml_experiments(workspace: Optional[str] = None) -> pd.DataFrame: def create_ml_experiment( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric ML experiment. @@ -70,13 +71,13 @@ def create_ml_experiment( Name of the ML experiment. description : str, default=None A description of the environment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -91,11 +92,11 @@ def create_ml_experiment( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' ML experiment has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' ML experiment has been created within the '{workspace_name}' workspace." ) -def delete_ml_experiment(name: str, workspace: Optional[str] = None): +def delete_ml_experiment(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric ML experiment. @@ -105,16 +106,16 @@ def delete_ml_experiment(name: str, workspace: Optional[str] = None): ---------- name: str Name of the ML experiment. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="MLExperiment", workspace=workspace + item_name=name, type="MLExperiment", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -124,5 +125,5 @@ def delete_ml_experiment(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' ML experiment within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' ML experiment within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_ml_models.py b/src/sempy_labs/_ml_models.py index f295f67..833d95e 100644 --- a/src/sempy_labs/_ml_models.py +++ b/src/sempy_labs/_ml_models.py @@ -8,9 +8,10 @@ pagination, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_ml_models(workspace: Optional[str] = None) -> pd.DataFrame: +def list_ml_models(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the ML models within a workspace. @@ -18,8 +19,8 @@ def list_ml_models(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -31,7 +32,7 @@ def list_ml_models(workspace: Optional[str] = None) -> pd.DataFrame: df = pd.DataFrame(columns=["ML Model Name", "ML Model Id", "Description"]) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/mlModels") @@ -57,7 +58,7 @@ def list_ml_models(workspace: Optional[str] = None) -> pd.DataFrame: def create_ml_model( - name: str, description: Optional[str] = None, workspace: Optional[str] = None + name: str, description: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Creates a Fabric ML model. @@ -70,13 +71,13 @@ def create_ml_model( Name of the ML model. description : str, default=None A description of the ML model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": name} @@ -89,11 +90,11 @@ def create_ml_model( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' ML model has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' ML model has been created within the '{workspace_name}' workspace." ) -def delete_ml_model(name: str, workspace: Optional[str] = None): +def delete_ml_model(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric ML model. @@ -103,13 +104,13 @@ def delete_ml_model(name: str, workspace: Optional[str] = None): ---------- name: str Name of the ML model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( item_name=name, type="MLModel", workspace=workspace @@ -122,5 +123,5 @@ def delete_ml_model(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' ML model within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' ML model within the '{workspace_name}' workspace has been deleted." ) diff --git a/src/sempy_labs/_model_dependencies.py b/src/sempy_labs/_model_dependencies.py index 4745826..102d99a 100644 --- a/src/sempy_labs/_model_dependencies.py +++ b/src/sempy_labs/_model_dependencies.py @@ -14,17 +14,17 @@ @log def get_measure_dependencies( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows all dependencies for all measures in a semantic model. Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -34,8 +34,6 @@ def get_measure_dependencies( Shows all dependencies for all measures in the semantic model. """ - workspace = fabric.resolve_workspace_name(workspace) - dep = fabric.evaluate_dax( dataset=dataset, workspace=workspace, @@ -164,11 +162,9 @@ def get_model_calc_dependencies( Shows all dependencies for all objects in the semantic model. """ - (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) dep = fabric.evaluate_dax( - dataset=dataset_id, - workspace=workspace_id, + dataset=dataset, + workspace=workspace, dax_string=""" SELECT [TABLE] AS [Table Name], @@ -238,39 +234,36 @@ def get_model_calc_dependencies( @log def measure_dependency_tree( - dataset: str, measure_name: str, workspace: Optional[str] = None + dataset: str | UUID, measure_name: str, workspace: Optional[str | UUID] = None ): """ Prints a measure dependency tree of all dependent objects for a measure in a semantic model. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. measure_name : str Name of the measure. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. - - Returns - ------- - """ - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - dfM = fabric.list_measures(dataset=dataset, workspace=workspace) + dfM = fabric.list_measures(dataset=dataset_id, workspace=workspace_id) dfM_filt = dfM[dfM["Measure Name"] == measure_name] if len(dfM_filt) == 0: print( - f"{icons.red_dot} The '{measure_name}' measure does not exist in the '{dataset}' semantic model in the '{workspace}' workspace." + f"{icons.red_dot} The '{measure_name}' measure does not exist in the '{dataset_name}' semantic model in the '{workspace_name}' workspace." ) return - md = get_measure_dependencies(dataset, workspace) + md = get_measure_dependencies(dataset_id, workspace_id) df_filt = md[md["Object Name"] == measure_name] # Create a dictionary to hold references to nodes diff --git a/src/sempy_labs/_notebooks.py b/src/sempy_labs/_notebooks.py index afcec7d..e077075 100644 --- a/src/sempy_labs/_notebooks.py +++ b/src/sempy_labs/_notebooks.py @@ -11,17 +11,18 @@ ) from sempy.fabric.exceptions import FabricHTTPException import os +from uuid import UUID _notebook_prefix = "notebook-content." def _get_notebook_definition_base( - notebook_name: str, workspace: Optional[str] = None + notebook_name: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=notebook_name, type="Notebook", workspace=workspace + item_name=notebook_name, type="Notebook", workspace=workspace_id ) client = fabric.FabricRestClient() response = client.post( @@ -33,7 +34,7 @@ def _get_notebook_definition_base( return pd.json_normalize(result["definition"]["parts"]) -def _get_notebook_type(notebook_name: str, workspace: Optional[str] = None) -> str: +def _get_notebook_type(notebook_name: str, workspace: Optional[str | UUID] = None) -> str: df_items = _get_notebook_definition_base( notebook_name=notebook_name, workspace=workspace @@ -49,7 +50,7 @@ def _get_notebook_type(notebook_name: str, workspace: Optional[str] = None) -> s def get_notebook_definition( - notebook_name: str, workspace: Optional[str] = None, decode: bool = True + notebook_name: str, workspace: Optional[str | UUID] = None, decode: bool = True ) -> str: """ Obtains the notebook definition. @@ -60,8 +61,8 @@ def get_notebook_definition( ---------- notebook_name : str The name of the notebook. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. decode : bool, default=True @@ -92,7 +93,7 @@ def import_notebook_from_web( notebook_name: str, url: str, description: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, overwrite: bool = False, ): """ @@ -110,16 +111,15 @@ def import_notebook_from_web( description : str, default=None The description of the notebook. Defaults to None which does not place a description. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. overwrite : bool, default=False If set to True, overwrites the existing notebook in the workspace if it exists. """ - if workspace is None: - workspace = fabric.resolve_workspace_name(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) # Fix links to go to the raw github file starting_text = "https://github.com/" @@ -139,7 +139,7 @@ def import_notebook_from_web( create_notebook( name=notebook_name, notebook_content=response.content, - workspace=workspace, + workspace=workspace_id, description=description, ) elif len(dfI_filt) > 0 and overwrite: @@ -149,7 +149,7 @@ def import_notebook_from_web( # ) else: raise ValueError( - f"{icons.red_dot} The '{notebook_name}' already exists within the '{workspace}' workspace and 'overwrite' is set to False." + f"{icons.red_dot} The '{notebook_name}' already exists within the '{workspace_name}' workspace and 'overwrite' is set to False." ) @@ -158,7 +158,7 @@ def create_notebook( notebook_content: str, type: str = "py", description: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates a new notebook with a definition within a workspace. @@ -174,13 +174,13 @@ def create_notebook( description : str, default=None The description of the notebook. Defaults to None which does not place a description. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() notebook_payload = base64.b64encode(notebook_content) @@ -205,12 +205,12 @@ def create_notebook( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{name}' notebook was created within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' notebook was created within the '{workspace_name}' workspace." ) def update_notebook_definition( - name: str, notebook_content: str, workspace: Optional[str] = None + name: str, notebook_content: str, workspace: Optional[str | UUID] = None ): """ Updates an existing notebook with a new definition. @@ -221,17 +221,17 @@ def update_notebook_definition( The name of the notebook to be updated. notebook_content : str The Jupyter notebook content (not in Base64 format). - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() notebook_payload = base64.b64encode(notebook_content) notebook_id = fabric.resolve_item_id( - item_name=name, type="Notebook", workspace=workspace + item_name=name, type="Notebook", workspace=workspace_id ) type = _get_notebook_type(notebook_name=name, workspace=workspace_id) @@ -256,5 +256,5 @@ def update_notebook_definition( lro(client, response, return_status_code=True) print( - f"{icons.green_dot} The '{name}' notebook was updated within the '{workspace}' workspace." + f"{icons.green_dot} The '{name}' notebook was updated within the '{workspace_name}' workspace." ) diff --git a/src/sempy_labs/_one_lake_integration.py b/src/sempy_labs/_one_lake_integration.py index 2f5263d..94e0242 100644 --- a/src/sempy_labs/_one_lake_integration.py +++ b/src/sempy_labs/_one_lake_integration.py @@ -2,13 +2,17 @@ import pandas as pd from typing import Optional from sempy._utils._log import log -from sempy_labs._helper_functions import resolve_workspace_name_and_id +from sempy_labs._helper_functions import ( + resolve_workspace_name_and_id, + resolve_dataset_name_and_id, +) import sempy_labs._icons as icons +from uuid import UUID @log def export_model_to_onelake( - dataset: str, + dataset: str | UUID, workspace: Optional[str] = None, destination_lakehouse: Optional[str] = None, destination_workspace: Optional[str] = None, @@ -30,22 +34,15 @@ def export_model_to_onelake( The name of the Fabric workspace in which the lakehouse resides. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) if destination_workspace is None: - destination_workspace = workspace + destination_workspace = workspace_name destination_workspace_id = workspace_id else: destination_workspace_id = fabric.resolve_workspace_id(destination_workspace) - dfD = fabric.list_datasets(workspace=workspace) - dfD_filt = dfD[dfD["Dataset Name"] == dataset] - - if len(dfD_filt) == 0: - raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model does not exist in the '{workspace}' workspace." - ) - tmsl = f""" {{ 'export': {{ @@ -53,7 +50,7 @@ def export_model_to_onelake( 'type': 'full', 'objects': [ {{ - 'database': '{dataset}' + 'database': '{dataset_name}' }} ] }} @@ -62,13 +59,13 @@ def export_model_to_onelake( # Export model's tables as delta tables try: - fabric.execute_tmsl(script=tmsl, workspace=workspace) + fabric.execute_tmsl(script=tmsl, workspace=workspace_id) print( - f"{icons.green_dot} The '{dataset}' semantic model's tables have been exported as delta tables to the '{workspace}' workspace.\n" + f"{icons.green_dot} The '{dataset_name}' semantic model's tables have been exported as delta tables to the '{workspace_name}' workspace.\n" ) except Exception as e: raise ValueError( - f"{icons.red_dot} The '{dataset}' semantic model's tables have not been exported as delta tables to the '{workspace}' workspace.\nMake sure you enable OneLake integration for the '{dataset}' semantic model. Follow the instructions here: https://learn.microsoft.com/power-bi/enterprise/onelake-integration-overview#enable-onelake-integration" + f"{icons.red_dot} The '{dataset_name}' semantic model's tables have not been exported as delta tables to the '{workspace_name}' workspace.\nMake sure you enable OneLake integration for the '{dataset_name}' semantic model. Follow the instructions here: https://learn.microsoft.com/power-bi/enterprise/onelake-integration-overview#enable-onelake-integration" ) from e # Create shortcuts if destination lakehouse is specified @@ -92,14 +89,14 @@ def export_model_to_onelake( destination_lakehouse_id = dfI_filt["Id"].iloc[0] # Source... - dfI_Source = fabric.list_items(workspace=workspace, type="SemanticModel") + dfI_Source = fabric.list_items(workspace=workspace_id, type="SemanticModel") dfI_filtSource = dfI_Source[(dfI_Source["Display Name"] == dataset)] sourceLakehouseId = dfI_filtSource["Id"].iloc[0] # Valid tables dfP = fabric.list_partitions( - dataset=dataset, - workspace=workspace, + dataset=dataset_id, + workspace=workspace_id, additional_xmla_properties=["Parent.SystemManaged"], ) dfP_filt = dfP[ @@ -107,7 +104,7 @@ def export_model_to_onelake( & (dfP["Source Type"] != "CalculationGroup") & (dfP["Parent System Managed"] == False) ] - dfC = fabric.list_columns(dataset=dataset, workspace=workspace) + dfC = fabric.list_columns(dataset=dataset_id, workspace=workspace_id) tmc = pd.DataFrame(dfP.groupby("Table Name")["Mode"].nunique()).reset_index() oneMode = tmc[tmc["Mode"] == 1] tableAll = dfP_filt[ @@ -141,7 +138,7 @@ def export_model_to_onelake( ) if response.status_code == 201: print( - f"{icons.bullet} The shortcut '{shortcutName}' was created in the '{destination_lakehouse}' lakehouse within the '{destination_workspace}' workspace. It is based on the '{tableName}' table in the '{dataset}' semantic model within the '{workspace}' workspace.\n" + f"{icons.bullet} The shortcut '{shortcutName}' was created in the '{destination_lakehouse}' lakehouse within the '{destination_workspace}' workspace. It is based on the '{tableName}' table in the '{dataset_name}' semantic model within the '{workspace_name}' workspace.\n" ) else: print(response.status_code) diff --git a/src/sempy_labs/_query_scale_out.py b/src/sempy_labs/_query_scale_out.py index 5f4a797..10f4b29 100644 --- a/src/sempy_labs/_query_scale_out.py +++ b/src/sempy_labs/_query_scale_out.py @@ -3,13 +3,15 @@ from sempy_labs._helper_functions import ( resolve_dataset_id, resolve_workspace_name_and_id, + resolve_dataset_name_and_id, ) from typing import Optional, Tuple import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def qso_sync(dataset: str, workspace: Optional[str] = None): +def qso_sync(dataset: str | UUID, workspace: Optional[str | UUID] = None): """ Triggers a query scale-out sync of read-only replicas for the specified dataset from the specified workspace. @@ -17,16 +19,16 @@ def qso_sync(dataset: str, workspace: Optional[str] = None): Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) client = fabric.PowerBIRestClient() response = client.post( @@ -36,12 +38,12 @@ def qso_sync(dataset: str, workspace: Optional[str] = None): if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} QSO sync initiated for the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} QSO sync initiated for the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) def qso_sync_status( - dataset: str, workspace: Optional[str] = None + dataset: str | UUID, workspace: Optional[str | UUID] = None ) -> Tuple[pd.DataFrame, pd.DataFrame]: """ Returns the query scale-out sync status for the specified dataset from the specified workspace. @@ -50,10 +52,10 @@ def qso_sync_status( Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -81,8 +83,8 @@ def qso_sync_status( columns=["Replica ID", "Replica Type", "Replica Version", "Replica Timestamp"] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) client = fabric.PowerBIRestClient() response = client.get( @@ -139,7 +141,7 @@ def qso_sync_status( return df, dfRep -def disable_qso(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: +def disable_qso(dataset: str | UUID, workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Sets the max read-only replicas to 0, disabling query scale out. @@ -147,10 +149,10 @@ def disable_qso(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - dataset : str - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -160,8 +162,8 @@ def disable_qso(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: A pandas dataframe showing the current query scale out settings. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) request_body = {"queryScaleOutSettings": {"maxReadOnlyReplicas": "0"}} @@ -172,20 +174,20 @@ def disable_qso(dataset: str, workspace: Optional[str] = None) -> pd.DataFrame: if response.status_code != 200: raise FabricHTTPException(response) - df = list_qso_settings(dataset=dataset, workspace=workspace) + df = list_qso_settings(dataset=dataset_id, workspace=workspace_id) print( - f"{icons.green_dot} Query scale out has been disabled for the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} Query scale out has been disabled for the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) return df def set_qso( - dataset: str, + dataset: str | UUID, auto_sync: bool = True, max_read_only_replicas: int = -1, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> pd.DataFrame: """ Sets the query scale out settings for a semantic model. @@ -194,14 +196,14 @@ def set_qso( Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. auto_sync : bool, default=True Whether the semantic model automatically syncs read-only replicas. max_read_only_replicas : int, default=-1 To enable semantic model scale-out, set max_read_only_replicas to -1, or any non-0 value. A value of -1 allows Power BI to create as many read-only replicas as your Power BI capacity supports. You can also explicitly set the replica count to a value lower than that of the capacity maximum. Setting max_read_only_replicas to -1 is recommended. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -213,16 +215,16 @@ def set_qso( from sempy_labs._helper_functions import is_default_semantic_model - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - if is_default_semantic_model(dataset=dataset, workspace=workspace): + if is_default_semantic_model(dataset=dataset_id, workspace=workspace_id): raise ValueError( f"{icons.red_dot} The 'set_qso' function does not run against default semantic models." ) if max_read_only_replicas == 0: - disable_qso(dataset=dataset, workspace=workspace) + disable_qso(dataset=dataset_id, workspace=workspace_id) return request_body = { @@ -232,12 +234,12 @@ def set_qso( } } - dfL = list_qso_settings(dataset=dataset, workspace=workspace) + dfL = list_qso_settings(dataset=dataset_id, workspace=workspace_id) storage_mode = dfL["Storage Mode"].iloc[0] if storage_mode == "Small": set_semantic_model_storage_format( - dataset=dataset, storage_format="Large", workspace=workspace + dataset=dataset_id, storage_format="Large", workspace=workspace_id ) client = fabric.PowerBIRestClient() @@ -248,34 +250,34 @@ def set_qso( if response.status_code != 200: raise FabricHTTPException(response) - df = list_qso_settings(dataset=dataset, workspace=workspace) + df = list_qso_settings(dataset=dataset_id, workspace=workspace_id) print( - f"{icons.green_dot} Query scale out has been set on the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.green_dot} Query scale out has been set on the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) return df def set_semantic_model_storage_format( - dataset: str, storage_format: str, workspace: Optional[str] = None + dataset: str | UUID, storage_format: str, workspace: Optional[str | UUID] = None ): """ Sets the semantic model storage format. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. storage_format : str The storage format for the semantic model. Valid options: 'Large', 'Small'. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) - dataset_id = resolve_dataset_id(dataset, workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) storage_format = storage_format.capitalize() @@ -295,12 +297,12 @@ def set_semantic_model_storage_format( f"{icons.red_dot} Invalid storage format value. Valid options: {storageFormats}." ) - dfL = list_qso_settings(dataset=dataset, workspace=workspace) + dfL = list_qso_settings(dataset=dataset_id, workspace=workspace_id) current_storage_format = dfL["Storage Mode"].iloc[0] if current_storage_format == storage_format: print( - f"{icons.info} The '{dataset}' semantic model within the '{workspace}' workspace is already set to '{storage_format.lower()}' storage format." + f"{icons.info} The '{dataset_name}' semantic model within the '{workspace_name}' workspace is already set to '{storage_format.lower()}' storage format." ) return @@ -311,22 +313,22 @@ def set_semantic_model_storage_format( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The semantic model storage format for the '{dataset}' semantic model within the '{workspace}' workspace has been set to '{storage_format}'." + f"{icons.green_dot} The semantic model storage format for the '{dataset_name}' semantic model within the '{workspace_name}' workspace has been set to '{storage_format}'." ) def list_qso_settings( - dataset: Optional[str] = None, workspace: Optional[str] = None + dataset: Optional[str | UUID] = None, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows the query scale out settings for a semantic model (or all semantic models within a workspace). Parameters ---------- - dataset : str, default=None - Name of the semantic model. - workspace : str, default=None - The Fabric workspace name. + dataset : str | UUID, default=None + Name or ID of the semantic model. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -336,10 +338,10 @@ def list_qso_settings( A pandas dataframe showing the query scale out settings. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) if dataset is not None: - dataset_id = resolve_dataset_id(dataset, workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) df = pd.DataFrame( columns=[ @@ -382,7 +384,7 @@ def list_qso_settings( def set_workspace_default_storage_format( - storage_format: str, workspace: Optional[str] = None + storage_format: str, workspace: Optional[str | UUID] = None ): """ Sets the default storage format for semantic models within a workspace. @@ -391,8 +393,8 @@ def set_workspace_default_storage_format( ---------- storage_format : str The storage format for the semantic model. Valid options: 'Large', 'Small'. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ @@ -408,22 +410,22 @@ def set_workspace_default_storage_format( f"{icons.red_dot} Invalid storage format. Please choose from these options: {storageFormats}." ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) # Check current storage format - dfW = fabric.list_workspaces(filter=f"name eq '{workspace}'") + dfW = fabric.list_workspaces(filter=f"name eq '{workspace_name}'") if len(dfW) == 0: raise ValueError() current_storage_format = dfW["Default Dataset Storage Format"].iloc[0] if current_storage_format == storage_format: print( - f"{icons.info} The '{workspace}' is already set to a default storage format of '{current_storage_format}'." + f"{icons.info} The '{workspace_name}' is already set to a default storage format of '{current_storage_format}'." ) return request_body = { - "name": workspace, + "name": workspace_name, "defaultDatasetStorageFormat": storage_format, } @@ -434,5 +436,5 @@ def set_workspace_default_storage_format( raise FabricHTTPException(response) print( - f"{icons.green_dot} The default storage format for the '{workspace}' workspace has been updated to '{storage_format}." + f"{icons.green_dot} The default storage format for the '{workspace_name}' workspace has been updated to '{storage_format}." ) diff --git a/src/sempy_labs/_refresh_semantic_model.py b/src/sempy_labs/_refresh_semantic_model.py index 28eaa38..1ac2b15 100644 --- a/src/sempy_labs/_refresh_semantic_model.py +++ b/src/sempy_labs/_refresh_semantic_model.py @@ -1,7 +1,6 @@ import sempy.fabric as fabric import time from sempy_labs._helper_functions import ( - resolve_dataset_id, resolve_workspace_name_and_id, _get_partition_map, _process_and_display_chart, @@ -278,39 +277,38 @@ def display_trace_logs(trace, partition_map, widget, title, stop=False): @log def cancel_dataset_refresh( - dataset: str, request_id: Optional[str] = None, workspace: Optional[str] = None + dataset: str | UUID, request_id: Optional[str] = None, workspace: Optional[str | UUID] = None ): """ Cancels the refresh of a semantic model which was executed via the `Enhanced Refresh API `_ Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. request_id : str, default=None The request id of a semantic model refresh. Defaults to finding the latest active refresh of the semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) - rr = fabric.list_refresh_requests(dataset=dataset, workspace=workspace) + rr = fabric.list_refresh_requests(dataset=dataset_id, workspace=workspace_id) rr_filt = rr[rr["Status"] == "Unknown"] if request_id is None: if len(rr_filt) == 0: raise ValueError( - f"{icons.red_dot} There are no active Enhanced API refreshes of the '{dataset}' semantic model within the '{workspace}' workspace." + f"{icons.red_dot} There are no active Enhanced API refreshes of the '{dataset_name}' semantic model within the '{workspace_name}' workspace." ) request_id = rr_filt["Request Id"].iloc[0] - dataset_id = resolve_dataset_id(dataset=dataset, workspace=workspace) - client = fabric.PowerBIRestClient() response = client.delete( @@ -320,12 +318,12 @@ def cancel_dataset_refresh( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{request_id}' refresh request for the '{dataset}' semantic model within the '{workspace}' workspace has been cancelled." + f"{icons.green_dot} The '{request_id}' refresh request for the '{dataset_name}' semantic model within the '{workspace_name}' workspace has been cancelled." ) def get_semantic_model_refresh_history( - dataset: str, request_id: Optional[str] = None, workspace: Optional[str] = None + dataset: str | UUID, request_id: Optional[str] = None, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Obtains the semantic model refresh history (refreshes executed via the Enhanced Refresh API). @@ -334,13 +332,13 @@ def get_semantic_model_refresh_history( Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. request_id : str, default=None The request id of a semantic model refresh. Defaults to None which resolves to showing all refresh requests for the given semantic model. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -350,8 +348,8 @@ def get_semantic_model_refresh_history( A pandas dataframe showing the semantic model refresh history. """ - workspace_name = fabric.resolve_workspace_name(workspace) - workspace_id = fabric.resolve_workspace_id(workspace_name) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) + (dataset_name, dataset_id) = resolve_dataset_name_and_id(dataset, workspace_id) df = pd.DataFrame( columns=[ "Request Id", @@ -363,9 +361,6 @@ def get_semantic_model_refresh_history( ] ) - dataset_id = fabric.resolve_item_id( - item_name=dataset, workspace=workspace_id, type="SemanticModel" - ) client = fabric.PowerBIRestClient() response = client.get( f"/v1.0/myorg/groups/{workspace_id}/datasets/{dataset_id}/refreshes" diff --git a/src/sempy_labs/_spark.py b/src/sempy_labs/_spark.py index 1a47e4c..fdd1608 100644 --- a/src/sempy_labs/_spark.py +++ b/src/sempy_labs/_spark.py @@ -6,9 +6,10 @@ resolve_workspace_name_and_id, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def list_custom_pools(workspace: Optional[str] = None) -> pd.DataFrame: +def list_custom_pools(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Lists all `custom pools `_ within a workspace. @@ -16,7 +17,7 @@ def list_custom_pools(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None + workspace : str | UUID, default=None The name of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -27,7 +28,7 @@ def list_custom_pools(workspace: Optional[str] = None) -> pd.DataFrame: A pandas dataframe showing all the custom pools within the Fabric workspace. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=[ @@ -95,7 +96,7 @@ def create_custom_pool( node_family: str = "MemoryOptimized", auto_scale_enabled: bool = True, dynamic_executor_allocation_enabled: bool = True, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates a `custom pool `_ within a workspace. @@ -122,13 +123,13 @@ def create_custom_pool( The status of `auto scale `_. dynamic_executor_allocation_enabled : bool, default=True The status of the `dynamic executor allocation `_. - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = { "name": pool_name, @@ -154,7 +155,7 @@ def create_custom_pool( if response.status_code != 201: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{pool_name}' spark pool has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{pool_name}' spark pool has been created within the '{workspace_name}' workspace." ) @@ -168,7 +169,7 @@ def update_custom_pool( node_family: Optional[str] = None, auto_scale_enabled: Optional[bool] = None, dynamic_executor_allocation_enabled: Optional[bool] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates the properties of a `custom pool `_ within a workspace. @@ -203,20 +204,20 @@ def update_custom_pool( dynamic_executor_allocation_enabled : bool, default=None The status of the `dynamic executor allocation `_. Defaults to None which keeps the existing property setting. - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = list_custom_pools(workspace=workspace) df_pool = df[df["Custom Pool Name"] == pool_name] if len(df_pool) == 0: raise ValueError( - f"{icons.red_dot} The '{pool_name}' custom pool does not exist within the '{workspace}'. Please choose a valid custom pool." + f"{icons.red_dot} The '{pool_name}' custom pool does not exist within the '{workspace_name}'. Please choose a valid custom pool." ) if node_family is None: @@ -262,11 +263,11 @@ def update_custom_pool( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{pool_name}' spark pool within the '{workspace}' workspace has been updated." + f"{icons.green_dot} The '{pool_name}' spark pool within the '{workspace_name}' workspace has been updated." ) -def delete_custom_pool(pool_name: str, workspace: Optional[str] = None): +def delete_custom_pool(pool_name: str, workspace: Optional[str | UUID] = None): """ Deletes a `custom pool `_ within a workspace. @@ -276,35 +277,35 @@ def delete_custom_pool(pool_name: str, workspace: Optional[str] = None): ---------- pool_name : str The custom pool name. - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) - dfL = list_custom_pools(workspace=workspace) + dfL = list_custom_pools(workspace=workspace_id) dfL_filt = dfL[dfL["Custom Pool Name"] == pool_name] if len(dfL_filt) == 0: raise ValueError( - f"{icons.red_dot} The '{pool_name}' custom pool does not exist within the '{workspace}' workspace." + f"{icons.red_dot} The '{pool_name}' custom pool does not exist within the '{workspace_name}' workspace." ) - poolId = dfL_filt["Custom Pool ID"].iloc[0] + pool_id = dfL_filt["Custom Pool ID"].iloc[0] client = fabric.FabricRestClient() - response = client.delete(f"/v1/workspaces/{workspace_id}/spark/pools/{poolId}") + response = client.delete(f"/v1/workspaces/{workspace_id}/spark/pools/{pool_id}") if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{pool_name}' spark pool has been deleted from the '{workspace}' workspace." + f"{icons.green_dot} The '{pool_name}' spark pool has been deleted from the '{workspace_name}' workspace." ) def get_spark_settings( - workspace: Optional[str] = None, return_dataframe: bool = True + workspace: Optional[str | UUID] = None, return_dataframe: bool = True ) -> pd.DataFrame | dict: """ Shows the spark settings for a workspace. @@ -313,8 +314,8 @@ def get_spark_settings( Parameters ---------- - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. return_dataframe : bool, default=True @@ -326,7 +327,7 @@ def get_spark_settings( A pandas dataframe showing the spark settings for a workspace. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame( columns=[ @@ -393,7 +394,7 @@ def update_spark_settings( max_executors: Optional[int] = None, environment_name: Optional[str] = None, runtime_version: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates the spark settings for a workspace. @@ -426,13 +427,13 @@ def update_spark_settings( runtime_version : str, default=None The `runtime version `_. Defaults to None which keeps the existing property setting. - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = get_spark_settings(workspace=workspace, return_dataframe=False) @@ -463,5 +464,5 @@ def update_spark_settings( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The spark settings within the '{workspace}' workspace have been updated accordingly." + f"{icons.green_dot} The spark settings within the '{workspace_name}' workspace have been updated accordingly." ) diff --git a/src/sempy_labs/_sql.py b/src/sempy_labs/_sql.py index c8ba89e..fdcd8a6 100644 --- a/src/sempy_labs/_sql.py +++ b/src/sempy_labs/_sql.py @@ -3,11 +3,10 @@ from typing import Optional, Union, List from sempy._utils._log import log import struct -import uuid from itertools import chain, repeat from sempy.fabric.exceptions import FabricHTTPException from sempy_labs._helper_functions import resolve_warehouse_id, resolve_lakehouse_id - +from uuid import UUID def _bytes2mswin_bstr(value: bytes) -> bytes: """Convert a sequence of bytes into a (MS-Windows) BSTR (as bytes). @@ -32,7 +31,7 @@ class ConnectBase: def __init__( self, name: str, - workspace: Optional[Union[str, uuid.UUID]] = None, + workspace: Optional[Union[str, UUID]] = None, timeout: Optional[int] = None, endpoint_type: str = "warehouse", ): @@ -139,7 +138,7 @@ class ConnectWarehouse(ConnectBase): def __init__( self, warehouse: str, - workspace: Optional[Union[str, uuid.UUID]] = None, + workspace: Optional[Union[str, UUID]] = None, timeout: Optional[int] = None, ): super().__init__( @@ -154,7 +153,7 @@ class ConnectLakehouse(ConnectBase): def __init__( self, lakehouse: str, - workspace: Optional[Union[str, uuid.UUID]] = None, + workspace: Optional[Union[str, UUID]] = None, timeout: Optional[int] = None, ): super().__init__( diff --git a/src/sempy_labs/_translations.py b/src/sempy_labs/_translations.py index 12db1c5..db4affd 100644 --- a/src/sempy_labs/_translations.py +++ b/src/sempy_labs/_translations.py @@ -3,29 +3,34 @@ from typing import List, Optional, Union from sempy._utils._log import log import sempy_labs._icons as icons -from sempy_labs._helper_functions import get_language_codes +from sempy_labs._helper_functions import ( + get_language_codes, + resolve_dataset_name_and_id, + resolve_workspace_name_and_id, +) +from uuid import UUID @log def translate_semantic_model( - dataset: str, + dataset: str | UUID, languages: Union[str, List[str]], exclude_characters: Optional[str] = None, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ) -> pd.DataFrame: """ Translates names, descriptions, display folders for all objects in a semantic model. Parameters ---------- - dataset : str - Name of the semantic model. + dataset : str | UUID + Name or ID of the semantic model. languages : str, List[str] The language code(s) in which to translate the semantic model. exclude_characters : str A string specifying characters which will be replaced by a space in the translation text when sent to the translation service. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. diff --git a/src/sempy_labs/_warehouses.py b/src/sempy_labs/_warehouses.py index 03d712a..e16216a 100644 --- a/src/sempy_labs/_warehouses.py +++ b/src/sempy_labs/_warehouses.py @@ -8,13 +8,14 @@ from typing import Optional import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID def create_warehouse( warehouse: str, description: Optional[str] = None, case_insensitive_collation: bool = False, - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Creates a Fabric warehouse. @@ -29,13 +30,13 @@ def create_warehouse( A description of the warehouse. case_insensitive_collation: bool, default=False If True, creates the warehouse with case-insensitive collation. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) request_body = {"displayName": warehouse} @@ -55,11 +56,11 @@ def create_warehouse( lro(client, response, status_codes=[201, 202]) print( - f"{icons.green_dot} The '{warehouse}' warehouse has been created within the '{workspace}' workspace." + f"{icons.green_dot} The '{warehouse}' warehouse has been created within the '{workspace_name}' workspace." ) -def list_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: +def list_warehouses(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the warehouses within a workspace. @@ -67,8 +68,8 @@ def list_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -89,7 +90,7 @@ def list_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: ] ) - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.get(f"/v1/workspaces/{workspace_id}/warehouses") @@ -115,7 +116,7 @@ def list_warehouses(workspace: Optional[str] = None) -> pd.DataFrame: return df -def delete_warehouse(name: str, workspace: Optional[str] = None): +def delete_warehouse(name: str, workspace: Optional[str | UUID] = None): """ Deletes a Fabric warehouse. @@ -125,16 +126,16 @@ def delete_warehouse(name: str, workspace: Optional[str] = None): ---------- name: str Name of the warehouse. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) item_id = fabric.resolve_item_id( - item_name=name, type="Warehouse", workspace=workspace + item_name=name, type="Warehouse", workspace=workspace_id ) client = fabric.FabricRestClient() @@ -144,12 +145,12 @@ def delete_warehouse(name: str, workspace: Optional[str] = None): raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{name}' warehouse within the '{workspace}' workspace has been deleted." + f"{icons.green_dot} The '{name}' warehouse within the '{workspace_name}' workspace has been deleted." ) def get_warehouse_tables( - warehouse: str, workspace: Optional[str] = None + warehouse: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of the tables in the Fabric warehouse. This function is based on INFORMATION_SCHEMA.TABLES. @@ -158,8 +159,8 @@ def get_warehouse_tables( ---------- warehouse : str Name of the Fabric warehouse. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -184,7 +185,7 @@ def get_warehouse_tables( def get_warehouse_columns( - warehouse: str, workspace: Optional[str] = None + warehouse: str, workspace: Optional[str | UUID] = None ) -> pd.DataFrame: """ Shows a list of the columns in each table within the Fabric warehouse. This function is based on INFORMATION_SCHEMA.COLUMNS. @@ -193,8 +194,8 @@ def get_warehouse_columns( ---------- warehouse : str Name of the Fabric warehouse. - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. diff --git a/src/sempy_labs/_workspace_identity.py b/src/sempy_labs/_workspace_identity.py index b342273..c46ac49 100644 --- a/src/sempy_labs/_workspace_identity.py +++ b/src/sempy_labs/_workspace_identity.py @@ -6,9 +6,10 @@ from typing import Optional import sempy_labs._icons as icons from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def provision_workspace_identity(workspace: Optional[str] = None): +def provision_workspace_identity(workspace: Optional[str | UUID] = None): """ Provisions a workspace identity for a workspace. @@ -16,13 +17,13 @@ def provision_workspace_identity(workspace: Optional[str] = None): Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.post(f"/v1/workspaces/{workspace_id}/provisionIdentity") @@ -33,11 +34,11 @@ def provision_workspace_identity(workspace: Optional[str] = None): lro(client, response) print( - f"{icons.green_dot} A workspace identity has been provisioned for the '{workspace}' workspace." + f"{icons.green_dot} A workspace identity has been provisioned for the '{workspace_name}' workspace." ) -def deprovision_workspace_identity(workspace: Optional[str] = None): +def deprovision_workspace_identity(workspace: Optional[str | UUID] = None): """ Deprovisions a workspace identity for a workspace. @@ -45,13 +46,13 @@ def deprovision_workspace_identity(workspace: Optional[str] = None): Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - workspace, workspace_id = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.post(f"/v1/workspaces/{workspace_id}/deprovisionIdentity") @@ -62,5 +63,5 @@ def deprovision_workspace_identity(workspace: Optional[str] = None): lro(client, response) print( - f"{icons.green_dot} The workspace identity has been deprovisioned from the '{workspace}' workspace." + f"{icons.green_dot} The workspace identity has been deprovisioned from the '{workspace_name}' workspace." ) diff --git a/src/sempy_labs/_workspaces.py b/src/sempy_labs/_workspaces.py index e2bec7b..365e210 100644 --- a/src/sempy_labs/_workspaces.py +++ b/src/sempy_labs/_workspaces.py @@ -8,9 +8,10 @@ resolve_capacity_id, ) from sempy.fabric.exceptions import FabricHTTPException +from uuid import UUID -def delete_user_from_workspace(email_address: str, workspace: Optional[str] = None): +def delete_user_from_workspace(email_address: str, workspace: Optional[str | UUID] = None): """ Removes a user from a workspace. @@ -20,13 +21,13 @@ def delete_user_from_workspace(email_address: str, workspace: Optional[str] = No ---------- email_address : str The email address of the user. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.PowerBIRestClient() response = client.delete(f"/v1.0/myorg/groups/{workspace_id}/users/{email_address}") @@ -34,7 +35,7 @@ def delete_user_from_workspace(email_address: str, workspace: Optional[str] = No if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{email_address}' user has been removed from accessing the '{workspace}' workspace." + f"{icons.green_dot} The '{email_address}' user has been removed from accessing the '{workspace_name}' workspace." ) @@ -42,7 +43,7 @@ def update_workspace_user( email_address: str, role_name: str, principal_type: Optional[str] = "User", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Updates a user's role within a workspace. @@ -57,13 +58,13 @@ def update_workspace_user( The `role `_ of the user within the workspace. principal_type : str, default='User' The `principal type `_. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) role_names = icons.workspace_roles role_name = role_name.capitalize() @@ -91,11 +92,11 @@ def update_workspace_user( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{email_address}' user has been updated to a '{role_name}' within the '{workspace}' workspace." + f"{icons.green_dot} The '{email_address}' user has been updated to a '{role_name}' within the '{workspace_name}' workspace." ) -def list_workspace_users(workspace: Optional[str] = None) -> pd.DataFrame: +def list_workspace_users(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ A list of all the users of a workspace and their roles. @@ -103,8 +104,8 @@ def list_workspace_users(workspace: Optional[str] = None) -> pd.DataFrame: Parameters ---------- - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -114,7 +115,7 @@ def list_workspace_users(workspace: Optional[str] = None) -> pd.DataFrame: A pandas dataframe the users of a workspace and their properties. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame(columns=["User Name", "Email Address", "Role", "Type", "User ID"]) client = fabric.FabricRestClient() @@ -143,7 +144,7 @@ def add_user_to_workspace( email_address: str, role_name: str, principal_type: Optional[str] = "User", - workspace: Optional[str] = None, + workspace: Optional[str | UUID] = None, ): """ Adds a user to a workspace. @@ -158,13 +159,13 @@ def add_user_to_workspace( The `role `_ of the user within the workspace. principal_type : str, default='User' The `principal type `_. - workspace : str, default=None - The name of the workspace. + workspace : str | UUID, default=None + The name or ID of the workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) role_names = icons.workspace_roles role_name = role_name.capitalize() @@ -196,11 +197,11 @@ def add_user_to_workspace( if response.status_code != 200: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{email_address}' user has been added as a{plural} '{role_name}' within the '{workspace}' workspace." + f"{icons.green_dot} The '{email_address}' user has been added as a{plural} '{role_name}' within the '{workspace_name}' workspace." ) -def assign_workspace_to_capacity(capacity_name: str, workspace: Optional[str] = None): +def assign_workspace_to_capacity(capacity_name: str, workspace: Optional[str | UUID] = None): """ Assigns a workspace to a capacity. @@ -210,13 +211,13 @@ def assign_workspace_to_capacity(capacity_name: str, workspace: Optional[str] = ---------- capacity_name : str The name of the capacity. - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) capacity_id = resolve_capacity_id(capacity_name=capacity_name) request_body = {"capacityId": capacity_id} @@ -230,11 +231,11 @@ def assign_workspace_to_capacity(capacity_name: str, workspace: Optional[str] = if response.status_code not in [200, 202]: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{workspace}' workspace has been assigned to the '{capacity_name}' capacity." + f"{icons.green_dot} The '{workspace_name}' workspace has been assigned to the '{capacity_name}' capacity." ) -def unassign_workspace_from_capacity(workspace: Optional[str] = None): +def unassign_workspace_from_capacity(workspace: Optional[str | UUID] = None): """ Unassigns a workspace from its assigned capacity. @@ -242,13 +243,13 @@ def unassign_workspace_from_capacity(workspace: Optional[str] = None): Parameters ---------- - workspace : str, default=None - The name of the Fabric workspace. + workspace : str | UUID, default=None + The name or ID of the Fabric workspace. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) client = fabric.FabricRestClient() response = client.post(f"/v1/workspaces/{workspace_id}/unassignFromCapacity") @@ -256,11 +257,11 @@ def unassign_workspace_from_capacity(workspace: Optional[str] = None): if response.status_code not in [200, 202]: raise FabricHTTPException(response) print( - f"{icons.green_dot} The '{workspace}' workspace has been unassigned from its capacity." + f"{icons.green_dot} The '{workspace_name}' workspace has been unassigned from its capacity." ) -def list_workspace_role_assignments(workspace: Optional[str] = None) -> pd.DataFrame: +def list_workspace_role_assignments(workspace: Optional[str | UUID] = None) -> pd.DataFrame: """ Shows the members of a given workspace. @@ -268,8 +269,8 @@ def list_workspace_role_assignments(workspace: Optional[str] = None) -> pd.DataF Parameters ---------- - workspace : str, default=None - The Fabric workspace name. + workspace : str | UUID, default=None + The Fabric workspace name or ID. Defaults to None which resolves to the workspace of the attached lakehouse or if no lakehouse attached, resolves to the workspace of the notebook. @@ -279,7 +280,7 @@ def list_workspace_role_assignments(workspace: Optional[str] = None) -> pd.DataF A pandas dataframe showing the members of a given workspace and their roles. """ - (workspace, workspace_id) = resolve_workspace_name_and_id(workspace) + (workspace_name, workspace_id) = resolve_workspace_name_and_id(workspace) df = pd.DataFrame(columns=["User Name", "User Email", "Role Name", "Type"]) diff --git a/src/sempy_labs/admin/_scanner.py b/src/sempy_labs/admin/_scanner.py index a20720a..92d9d07 100644 --- a/src/sempy_labs/admin/_scanner.py +++ b/src/sempy_labs/admin/_scanner.py @@ -5,8 +5,10 @@ import numpy as np import time from sempy_labs.admin._basic_functions import list_workspaces +from sempy._utils._log import log +@log def scan_workspaces( data_source_details: bool = False, dataset_schema: bool = False, From d43e10f1ebd4c87d70c81de450a6a0bd85ce1639 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 17 Dec 2024 15:01:18 +0200 Subject: [PATCH 6/6] fixed unit test --- src/sempy_labs/lakehouse/_shortcuts.py | 2 +- tests/test_shortcuts.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sempy_labs/lakehouse/_shortcuts.py b/src/sempy_labs/lakehouse/_shortcuts.py index 5789ed2..5627fff 100644 --- a/src/sempy_labs/lakehouse/_shortcuts.py +++ b/src/sempy_labs/lakehouse/_shortcuts.py @@ -43,7 +43,7 @@ def create_shortcut_onelake( (source_workspace_name, source_workspace_id) = resolve_workspace_name_and_id(source_workspace) source_lakehouse_id = resolve_lakehouse_id(source_lakehouse, source_workspace_id) - source_lakehouse_name = fabric.resolve_item_name(item_id=source_lakehouse_id, type='Lakehouse', workspace=destination_workspace_id) + source_lakehouse_name = fabric.resolve_item_name(item_id=source_lakehouse_id, type='Lakehouse', workspace=source_workspace_id) if destination_workspace is None: destination_workspace_name = source_workspace_name diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index c45d1d5..fa1844e 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,14 +1,14 @@ import pandas as pd -from json import loads from sempy_labs.lakehouse._shortcuts import create_shortcut_onelake from unittest.mock import MagicMock, PropertyMock, patch +@patch("sempy.fabric.resolve_item_name") @patch("sempy.fabric.list_items") @patch("sempy.fabric.resolve_workspace_id") @patch("sempy.fabric.resolve_item_id") @patch("sempy.fabric.FabricRestClient") -def test_create_shortcut_onelake(fabric_rest_client_mock, resolve_item_id_mock, resolve_workspace_id_mock, list_items_mock): +def test_create_shortcut_onelake(fabric_rest_client_mock, resolve_item_id_mock, resolve_workspace_id_mock, list_items_mock, resolve_item_name_mock): # prepare mocks def resolve_workspace_id_mock_side_effect(workspace_name): if workspace_name == "source_workspace": @@ -22,22 +22,23 @@ def resolve_workspace_id_mock_side_effect(workspace_name): resolve_workspace_id_mock.side_effect = resolve_workspace_id_mock_side_effect resolve_item_id_mock.return_value = "00000000-0000-0000-0000-00000000000A" + resolve_item_name_mock.return_value = "My item" def list_items_side_effect(type, workspace): assert type == "Lakehouse" if workspace == "source_workspace": return pd.DataFrame([{ - "Display Name": "source_lakehouse", + "Display Name": "source_lakehouse_id", "Id": "10000000-0000-0000-0000-000000000001" }]) if workspace == "destination_workspace": return pd.DataFrame([{ - "Display Name": "destination_lakehouse", + "Display Name": "destination_lakehouse_id", "Id": "20000000-0000-0000-0000-000000000002" }]) - + assert False, f"Unexpected workspace: {workspace}" list_items_mock.side_effect = list_items_side_effect