diff --git a/docs/content/en/latest/workspace-content/analytics-model/store_analytics_model_to_disk.md b/docs/content/en/latest/workspace-content/analytics-model/store_analytics_model_to_disk.md index 63e7327e2..d1f5ea5df 100644 --- a/docs/content/en/latest/workspace-content/analytics-model/store_analytics_model_to_disk.md +++ b/docs/content/en/latest/workspace-content/analytics-model/store_analytics_model_to_disk.md @@ -5,7 +5,7 @@ weight: 131 superheading: "catalog_workspace_content." --- -``store_analytics_model_to_disk(workspace_id: str, path: Path = Path.cwd())`` +``store_analytics_model_to_disk(workspace_id: str, path: Path = Path.cwd(), exclude: Optional[list[str]] = None, sort: bool = False)`` Stores analytics model for a given workspace in directory hierarchy.This method does not tie the declarative analytics model to the workspace and organization, thus it is recommended for migration between workspaces. If you want to backup analytics model between workspaces or organizations, use [store_analytics_model_to_disk](../store_analytics_model_to_disk/). @@ -31,6 +31,12 @@ Workspace identification string e.g. "demo" {{< parameter p_name="path" p_type="Optional[Path]" >}} Path to the root of the layout directory. Defaults to Path.cwd(). {{< /parameter >}} +{{< parameter p_name="exclude" p_type=" Optional[list[str]]" >}} +Defines properties which should not be included in the result. E.g.: ["ACTIVITY_INFO"] – refers to createdBy, etc. Default is None. +{{< /parameter >}} +{{< parameter p_name="sort" p_type="bool" >}} +Flag if the output should be sorted before storing to disk. Default is False. +{{< /parameter >}} {{% /parameters-block %}} {{% parameters-block title="Returns" None="yes" %}} diff --git a/docs/content/en/latest/workspace-content/logical-data-model/store_ldm_to_disk.md b/docs/content/en/latest/workspace-content/logical-data-model/store_ldm_to_disk.md index 0c4a36679..1f7f695e9 100644 --- a/docs/content/en/latest/workspace-content/logical-data-model/store_ldm_to_disk.md +++ b/docs/content/en/latest/workspace-content/logical-data-model/store_ldm_to_disk.md @@ -6,7 +6,7 @@ superheading: "catalog_workspace_content." --- -``store_ldm_to_disk(workspace_id: str, path: Path = Path.cwd())`` +``store_ldm_to_disk(workspace_id: str, path: Path = Path.cwd(), sort: bool = False)`` Stores the declarative logical data model for a given workspace in directory hierarchy. This method does not tie the LDM to the workspace and organization, thus it is recommended for migration between organizations. If you want to backup LDM use [store_declarative_ldm](../store_declarative_ldm/). @@ -26,6 +26,9 @@ Workspace identification string e.g. "demo" {{< parameter p_name="path" p_type="Optional[Path]" >}} Path to the root of the layout directory. Defaults to Path.cwd(). {{< /parameter >}} +{{< parameter p_name="sort" p_type="bool" >}} +Flag if the output should be sorted before storing to disk. Default is False. +{{< /parameter >}} {{% /parameters-block %}} {{% parameters-block title="Returns" None="yes" %}} diff --git a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py index 2f4aab7bb..94bcb8cb2 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py +++ b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/data_source.py @@ -110,11 +110,11 @@ def client_class() -> type[DeclarativeDataSources]: def data_sources_folder(layout_organization_folder: Path) -> Path: return layout_organization_folder / LAYOUT_DATA_SOURCES_DIR - def store_to_disk(self, layout_organization_folder: Path) -> None: + def store_to_disk(self, layout_organization_folder: Path, sort: bool = False) -> None: data_sources_folder = self.data_sources_folder(layout_organization_folder) create_directory(data_sources_folder) for data_source in self.data_sources: - data_source.store_to_disk(data_sources_folder) + data_source.store_to_disk(data_sources_folder, sort=sort) @classmethod def load_from_disk(cls, layout_organization_folder: Path) -> CatalogDeclarativeDataSources: @@ -201,12 +201,12 @@ def to_api( dictionary["client_secret"] = client_secret return self.client_class().from_dict(dictionary) - def store_to_disk(self, data_sources_folder: Path) -> None: + def store_to_disk(self, data_sources_folder: Path, sort: bool = False) -> None: data_source_folder = self.data_source_folder(data_sources_folder, self.id) file_path = data_source_folder / f"{self.id}.yaml" data_source_dict = self.to_api().to_dict(camel_case=True) - write_layout_to_file(file_path, data_source_dict) + write_layout_to_file(file_path, data_source_dict, sort=sort) @classmethod def load_from_disk(cls, data_sources_folder: Path, data_source_id: str) -> CatalogDeclarativeDataSource: diff --git a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/physical_model/table.py b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/physical_model/table.py index 35690c180..ea80d4a15 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/physical_model/table.py +++ b/gooddata-sdk/gooddata_sdk/catalog/data_source/declarative_model/physical_model/table.py @@ -25,10 +25,10 @@ class CatalogDeclarativeTable(Base): def client_class() -> builtins.type[DeclarativeTable]: return DeclarativeTable - def store_to_disk(self, pdm_folder: Path) -> None: + def store_to_disk(self, pdm_folder: Path, sort: bool = False) -> None: table_dict = self.to_api().to_dict(camel_case=True) table_file_path = pdm_folder / f"{self.id}.yaml" - write_layout_to_file(table_file_path, table_dict) + write_layout_to_file(table_file_path, table_dict, sort=sort) @classmethod def load_from_disk(cls, table_file_path: Path) -> CatalogDeclarativeTable: diff --git a/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user.py b/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user.py index 07dce99b4..ebabafd0b 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user.py +++ b/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user.py @@ -34,12 +34,12 @@ def load_from_disk(cls, layout_organization_folder: Path) -> CatalogDeclarativeU users = [CatalogDeclarativeUser.from_dict(record, camel_case=True) for record in data] return cls(users=users) - def store_to_disk(self, layout_organization_folder: Path) -> None: + def store_to_disk(self, layout_organization_folder: Path, sort: bool = False) -> None: users_directory = layout_organization_folder / LAYOUT_USERS_DIR users_file = users_directory / LAYOUT_USERS_FILE create_directory(users_directory) users = [user.to_dict(camel_case=True) for user in self.users] - write_layout_to_file(users_file, users) + write_layout_to_file(users_file, users, sort=sort) @attr.s(auto_attribs=True, kw_only=True) diff --git a/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user_group.py b/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user_group.py index 4b5c74f51..da514ca8d 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user_group.py +++ b/gooddata-sdk/gooddata_sdk/catalog/user/declarative_model/user_group.py @@ -33,12 +33,12 @@ def load_from_disk(cls, layout_organization_folder: Path) -> CatalogDeclarativeU user_groups = [CatalogDeclarativeUserGroup.from_dict(record, camel_case=True) for record in data] return cls(user_groups=user_groups) - def store_to_disk(self, layout_organization_folder: Path) -> None: + def store_to_disk(self, layout_organization_folder: Path, sort: bool = False) -> None: user_groups_directory = layout_organization_folder / LAYOUT_USER_GROUPS_DIR user_groups_file = user_groups_directory / LAYOUT_USER_GROUPS_FILE create_directory(user_groups_directory) user_groups = [user_group.to_dict(camel_case=True) for user_group in self.user_groups] - write_layout_to_file(user_groups_file, user_groups) + write_layout_to_file(user_groups_file, user_groups, sort=sort) @attr.s(auto_attribs=True, kw_only=True) diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/content_service.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/content_service.py index 8d4a9c9b5..99e130bfd 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/content_service.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/content_service.py @@ -358,7 +358,7 @@ def load_and_put_declarative_ldm( declarative_ldm = self.load_declarative_ldm(workspace_id, layout_root_path) self.put_declarative_ldm(workspace_id, declarative_ldm, validator, standalone_copy) - def store_ldm_to_disk(self, workspace_id: str, path: Path = Path.cwd()) -> None: + def store_ldm_to_disk(self, workspace_id: str, path: Path = Path.cwd(), sort: bool = False) -> None: """Store declarative logical data model for a given workspace in directory hierarchy. This method does not tie the LDM to the workspace and organization, thus it is recommended for migration between organizations. If you want to backup LDM use store_declarative_ldm. @@ -368,11 +368,13 @@ def store_ldm_to_disk(self, workspace_id: str, path: Path = Path.cwd()) -> None: Workspace identification string e.g. "demo" path (Path, optional): Path to the root of the layout directory. Defaults to Path.cwd(). + sort (bool, optional): + Flag if the output should be sorted before storing to disk. Default is False. Returns: None """ - self.get_declarative_ldm(workspace_id).store_to_disk(path) + self.get_declarative_ldm(workspace_id).store_to_disk(path, sort=sort) @staticmethod def load_ldm_from_disk(path: Path = Path.cwd()) -> CatalogDeclarativeModel: @@ -479,7 +481,7 @@ def load_and_put_declarative_analytics_model(self, workspace_id: str, layout_roo self.put_declarative_analytics_model(workspace_id, declarative_analytics_model) def store_analytics_model_to_disk( - self, workspace_id: str, path: Path = Path.cwd(), exclude: Optional[list[str]] = None + self, workspace_id: str, path: Path = Path.cwd(), exclude: Optional[list[str]] = None, sort: bool = False ) -> None: """Store analytics model for a given workspace in directory hierarchy.This method does not tie the declarative analytics model to the workspace and organization, thus it is recommended for migration between workspaces. @@ -491,12 +493,14 @@ def store_analytics_model_to_disk( path (Path, optional): Path to the root of the layout directory. Defaults to Path.cwd(). exclude (Optional[list[str]]): - Defines properties which should not be included in the payload. E.g.: ["ACTIVITY_INFO"] + Defines properties which should not be included in the result. E.g.: ["ACTIVITY_INFO"] – refers to createdBy, etc. Default is None. + sort (bool, optional): + Flag if the output should be sorted before storing to disk. Default is False. Returns: None """ - self.get_declarative_analytics_model(workspace_id, exclude).store_to_disk(path) + self.get_declarative_analytics_model(workspace_id, exclude).store_to_disk(path, sort=sort) @staticmethod def load_analytics_model_from_disk(path: Path = Path.cwd()) -> CatalogDeclarativeAnalytics: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/analytics_model.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/analytics_model.py index 6181e6570..ffd7606a1 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/analytics_model.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/analytics_model.py @@ -58,9 +58,9 @@ class CatalogDeclarativeAnalytics(Base): def client_class() -> type[DeclarativeAnalytics]: return DeclarativeAnalytics - def store_to_disk(self, workspace_folder: Path) -> None: + def store_to_disk(self, workspace_folder: Path, sort: bool = False) -> None: if self.analytics is not None: - self.analytics.store_to_disk(workspace_folder) + self.analytics.store_to_disk(workspace_folder, sort=sort) @classmethod def load_from_disk(cls, workspace_folder: Path) -> CatalogDeclarativeAnalytics: @@ -137,7 +137,7 @@ def get_export_definition_dif(analytics_model_folder: Path) -> Path: create_directory(folder) return folder - def store_to_disk(self, workspace_folder: Path) -> None: + def store_to_disk(self, workspace_folder: Path, sort: bool = False) -> None: analytics_model_folder = self.get_analytics_model_folder(workspace_folder) analytical_dashboards_folder = self.get_analytical_dashboards_folder(analytics_model_folder) @@ -150,28 +150,28 @@ def store_to_disk(self, workspace_folder: Path) -> None: export_definition_folder = self.get_export_definition_dif(analytical_dashboards_folder) for analytical_dashboard in self.analytical_dashboards: - analytical_dashboard.store_to_disk(analytical_dashboards_folder) + analytical_dashboard.store_to_disk(analytical_dashboards_folder, sort=sort) for analytical_dashboard_extension in self.analytical_dashboard_extensions: - analytical_dashboard_extension.store_to_disk(analytical_dashboard_extensions_folder) + analytical_dashboard_extension.store_to_disk(analytical_dashboard_extensions_folder, sort=sort) for dashboard_plugin in self.dashboard_plugins: - dashboard_plugin.store_to_disk(dashboard_plugins_folder) + dashboard_plugin.store_to_disk(dashboard_plugins_folder, sort=sort) for filter_context in self.filter_contexts: - filter_context.store_to_disk(filter_contexts_folder) + filter_context.store_to_disk(filter_contexts_folder, sort=sort) for metric in self.metrics: - metric.store_to_disk(metrics_folder) + metric.store_to_disk(metrics_folder, sort=sort) for visualization_object in self.visualization_objects: - visualization_object.store_to_disk(visualization_objects_folder) + visualization_object.store_to_disk(visualization_objects_folder, sort=sort) for attribute_hierarchy in self.attribute_hierarchies: - attribute_hierarchy.store_to_disk(attribute_hierarchy_folder) + attribute_hierarchy.store_to_disk(attribute_hierarchy_folder, sort=sort) for export_definition in self.export_definitions: - export_definition.store_to_disk(export_definition_folder) + export_definition.store_to_disk(export_definition_folder, sort=sort) @classmethod def load_from_disk(cls, workspace_folder: Path) -> CatalogDeclarativeAnalyticsLayer: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/base.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/base.py index 0016b2b83..3fc4bf805 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/base.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/analytics_model/base.py @@ -15,9 +15,9 @@ class CatalogAnalyticsObjectBase(Base): id: str - def store_to_disk(self, analytics_folder: Path) -> None: + def store_to_disk(self, analytics_folder: Path, sort: bool = False) -> None: analytics_file = analytics_folder / f"{self.id}.yaml" - write_layout_to_file(analytics_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(analytics_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls: type[T], analytics_file: Path) -> T: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py index f12c93ec3..3f5b4be3a 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset/dataset.py @@ -53,9 +53,9 @@ class CatalogDeclarativeDataset(Base): def client_class() -> type[DeclarativeDataset]: return DeclarativeDataset - def store_to_disk(self, datasets_folder: Path) -> None: + def store_to_disk(self, datasets_folder: Path, sort: bool = False) -> None: dataset_file = datasets_folder / f"{self.id}.yaml" - write_layout_to_file(dataset_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(dataset_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, dataset_file: Path) -> CatalogDeclarativeDataset: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/dataset_extension.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/dataset_extension.py index 6b798f1d7..d33f1590c 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/dataset_extension.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/dataset_extensions/dataset_extension.py @@ -22,9 +22,9 @@ class CatalogDeclarativeDatasetExtension(Base): def client_class() -> type[DeclarativeDatasetExtension]: return DeclarativeDatasetExtension - def store_to_disk(self, dataset_extension_folder: Path) -> None: + def store_to_disk(self, dataset_extension_folder: Path, sort: bool = False) -> None: dataset_extension_file = dataset_extension_folder / f"{self.id}.yaml" - write_layout_to_file(dataset_extension_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(dataset_extension_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, dataset_extension_file: Path) -> "CatalogDeclarativeDatasetExtension": diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/date_dataset.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/date_dataset.py index db45d2211..7a2270581 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/date_dataset.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/date_dataset/date_dataset.py @@ -27,9 +27,9 @@ class CatalogDeclarativeDateDataset(Base): def client_class() -> type[DeclarativeDateDataset]: return DeclarativeDateDataset - def store_to_disk(self, date_instances_folder: Path) -> None: + def store_to_disk(self, date_instances_folder: Path, sort: bool = False) -> None: date_instance_file = date_instances_folder / f"{self.id}.yaml" - write_layout_to_file(date_instance_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(date_instance_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, date_instance_file: Path) -> CatalogDeclarativeDateDataset: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/ldm.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/ldm.py index f6b1e370c..2436c9939 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/ldm.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/logical_model/ldm.py @@ -34,9 +34,9 @@ class CatalogDeclarativeModel(Base): def client_class() -> type[DeclarativeModel]: return DeclarativeModel - def store_to_disk(self, workspace_folder: Path) -> None: + def store_to_disk(self, workspace_folder: Path, sort: bool = False) -> None: if self.ldm is not None: - self.ldm.store_to_disk(workspace_folder) + self.ldm.store_to_disk(workspace_folder, sort=sort) @classmethod def load_from_disk(cls, workspace_folder: Path) -> CatalogDeclarativeModel: @@ -106,20 +106,20 @@ def create_dataset_extensions_folder(ldm_folder: Path) -> Path: create_directory(folder) return folder - def store_to_disk(self, workspace_folder: Path) -> None: + def store_to_disk(self, workspace_folder: Path, sort: bool = False) -> None: ldm_folder = self.create_ldm_folder(workspace_folder) datasets_folder = self.create_datasets_folder(ldm_folder) date_instances_folder = self.create_date_instances_folder(ldm_folder) for dataset in self.datasets: - dataset.store_to_disk(datasets_folder) + dataset.store_to_disk(datasets_folder, sort=sort) for date_instance in self.date_instances: - date_instance.store_to_disk(date_instances_folder) + date_instance.store_to_disk(date_instances_folder, sort=sort) # Note: should be defaulted to an empty list in the future if self.dataset_extensions: dataset_extensions_folder = self.create_dataset_extensions_folder(ldm_folder) for dataset_extension in self.dataset_extensions: - dataset_extension.store_to_disk(dataset_extensions_folder) + dataset_extension.store_to_disk(dataset_extensions_folder, sort=sort) @classmethod def load_from_disk(cls, workspace_folder: Path) -> CatalogDeclarativeLdm: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py index ccb37ab6c..d131fa8fb 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/declarative_model/workspace/workspace.py @@ -54,11 +54,11 @@ class CatalogDeclarativeWorkspaceModel(Base): def client_class() -> type[DeclarativeWorkspaceModel]: return DeclarativeWorkspaceModel - def store_to_disk(self, workspace_folder: Path) -> None: + def store_to_disk(self, workspace_folder: Path, sort: bool = False) -> None: if self.ldm is not None: - self.ldm.store_to_disk(workspace_folder) + self.ldm.store_to_disk(workspace_folder, sort=sort) if self.analytics is not None: - self.analytics.store_to_disk(workspace_folder) + self.analytics.store_to_disk(workspace_folder, sort=sort) @classmethod def load_from_disk(cls, workspace_folder: Path) -> CatalogDeclarativeWorkspaceModel: @@ -102,16 +102,16 @@ def to_api(self, include_nested_structures: bool = True) -> DeclarativeWorkspace del dictionary["model"] return client_class.from_dict(dictionary, camel_case=False) - def store_to_disk(self, workspaces_folder: Path) -> None: + def store_to_disk(self, workspaces_folder: Path, sort: bool = False) -> None: workspace_folder = workspaces_folder / self.id file_path = workspace_folder / f"{self.id}.yaml" create_directory(workspace_folder) workspace_dict = self.to_api(include_nested_structures=False).to_dict(camel_case=True) - write_layout_to_file(file_path, workspace_dict) + write_layout_to_file(file_path, workspace_dict, sort=sort) if self.model is not None: - self.model.store_to_disk(workspace_folder) + self.model.store_to_disk(workspace_folder, sort) @classmethod def load_from_disk(cls, workspaces_folder: Path, workspace_id: str) -> CatalogDeclarativeWorkspace: @@ -204,9 +204,9 @@ class CatalogDeclarativeWorkspaceDataFilter(Base): def client_class() -> type[DeclarativeWorkspaceDataFilter]: return DeclarativeWorkspaceDataFilter - def store_to_disk(self, workspaces_data_filters_folder: Path) -> None: + def store_to_disk(self, workspaces_data_filters_folder: Path, sort: bool = False) -> None: workspaces_data_filter_file = workspaces_data_filters_folder / f"{self.id}.yaml" - write_layout_to_file(workspaces_data_filter_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(workspaces_data_filter_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, workspaces_data_filter_file: Path) -> CatalogDeclarativeWorkspaceDataFilter: @@ -267,9 +267,9 @@ class CatalogDeclarativeUserDataFilter(Base): def client_class() -> type[DeclarativeUserDataFilter]: return DeclarativeUserDataFilter - def store_to_disk(self, user_data_filters_folder: Path) -> None: + def store_to_disk(self, user_data_filters_folder: Path, sort: bool = False) -> None: user_data_filter_file = user_data_filters_folder / f"{self.id}.yaml" - write_layout_to_file(user_data_filter_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(user_data_filter_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, user_data_filter_file: Path) -> CatalogDeclarativeUserDataFilter: @@ -306,9 +306,9 @@ class CatalogDeclarativeFilterView(Base): def client_class() -> type[DeclarativeFilterView]: return DeclarativeFilterView - def store_to_disk(self, filter_views_folder: Path) -> None: + def store_to_disk(self, filter_views_folder: Path, sort: bool = False) -> None: filter_view_file = filter_views_folder / f"{self.id}.yaml" - write_layout_to_file(filter_view_file, self.to_api().to_dict(camel_case=True)) + write_layout_to_file(filter_view_file, self.to_api().to_dict(camel_case=True), sort=sort) @classmethod def load_from_disk(cls, filter_view_file: Path) -> CatalogDeclarativeFilterView: diff --git a/gooddata-sdk/gooddata_sdk/catalog/workspace/service.py b/gooddata-sdk/gooddata_sdk/catalog/workspace/service.py index 4d843b6ef..79b8efc07 100644 --- a/gooddata-sdk/gooddata_sdk/catalog/workspace/service.py +++ b/gooddata-sdk/gooddata_sdk/catalog/workspace/service.py @@ -666,6 +666,7 @@ def translate_if_requested( from_lang: str, already_translated: dict[str, str], translation_file_path: Path, + sort: bool = False, ) -> dict[str, str]: if to_translate and translator_func: translated = { @@ -673,7 +674,7 @@ def translate_if_requested( **already_translated, } # Write translation file - write_layout_to_file(translation_file_path, translated) + write_layout_to_file(translation_file_path, translated, sort=sort) elif already_translated: logger.info("Nothing to translate, but translation file exists, so we can apply it.") translated = already_translated @@ -686,7 +687,7 @@ def translate_if_requested( translated = {} for x in to_translate: translated[x] = x - write_layout_to_file(translation_file_path, translated) + write_layout_to_file(translation_file_path, translated, sort=sort) return translated @staticmethod diff --git a/gooddata-sdk/gooddata_sdk/utils.py b/gooddata-sdk/gooddata_sdk/utils.py index 17bf59045..8632a66ab 100644 --- a/gooddata-sdk/gooddata_sdk/utils.py +++ b/gooddata-sdk/gooddata_sdk/utils.py @@ -5,7 +5,7 @@ import json import os import re -from collections.abc import KeysView +from collections.abc import KeysView, Mapping from enum import Enum, auto from pathlib import Path from shutil import rmtree @@ -182,9 +182,18 @@ def increase_indent(self, flow: bool = False, indentless: bool = False): return super().increase_indent(flow, False) -def write_layout_to_file(path: Path, content: Union[dict[str, Any], list[dict]]) -> None: +def write_layout_to_file(path: Path, content: Union[dict[str, Any], list[dict]], sort: bool = False) -> None: + """ + Write content to a YAML file. + + Args: + path (Path): The path to the file where the content will be written. + content (Union[dict[str, Any], list[dict]]): The content to write to the file. + sort (bool): If True, the content will be sorted before writing. Defaults to False + """ + content_to_store = deep_sort(content) if sort else content with open(path, "w", encoding="utf-8") as fp: - yaml.dump(content, fp, indent=2, Dumper=IndentDumper, allow_unicode=True) + yaml.dump(content_to_store, fp, indent=2, Dumper=IndentDumper, allow_unicode=True) def read_layout_from_file(path: Path) -> Any: @@ -451,3 +460,21 @@ def filter_for_attributes_labels(attributes: list[Attribute], character_limit: i if current_batch: # Add remaining batch queries.append(f"labels.id=in=({','.join(current_batch)})") return queries + + +def deep_sort(obj: Any) -> Any: + """ + Recursively sort dictionaries by key. The order of lists and tuples is preserved. + """ + if isinstance(obj, Mapping): + # Sort dict by keys, deep-sorting values + return {k: deep_sort(v) for k, v in sorted(obj.items(), key=lambda kv: kv[0])} + elif isinstance(obj, list): + # Deep-sort elements first + list_items = [deep_sort(x) for x in obj] + return list_items + elif isinstance(obj, tuple): + tuple_items = tuple(deep_sort(x) for x in obj) + return tuple_items + else: + return obj