Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions packages/gooddata-sdk/src/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,25 +180,6 @@
CatalogWorkspacePermissionAssignment,
)
from gooddata_sdk.catalog.validate_by_item import CatalogValidateByItem
from gooddata_sdk.catalog.workspace.aac import (
aac_attribute_hierarchy_to_declarative,
aac_dashboard_to_declarative,
aac_dataset_to_declarative,
aac_date_dataset_to_declarative,
aac_metric_to_declarative,
aac_plugin_to_declarative,
aac_visualization_to_declarative,
declarative_attribute_hierarchy_to_aac,
declarative_dashboard_to_aac,
declarative_dataset_to_aac,
declarative_date_instance_to_aac,
declarative_metric_to_aac,
declarative_plugin_to_aac,
declarative_visualization_to_aac,
detect_yaml_format,
load_aac_workspace_from_disk,
store_aac_workspace_to_disk,
)
from gooddata_sdk.catalog.workspace.content_service import CatalogWorkspaceContent, CatalogWorkspaceContentService
from gooddata_sdk.catalog.workspace.declarative_model.workspace.analytics_model.analytics_model import (
CatalogDeclarativeAnalytics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from gooddata_sdk import CatalogDeclarativeAutomation
from gooddata_sdk.catalog.catalog_service_base import CatalogServiceBase
from gooddata_sdk.catalog.permission.service import CatalogPermissionService
from gooddata_sdk.catalog.workspace.aac import load_aac_workspace_from_disk, store_aac_workspace_to_disk
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import (
CatalogDeclarativeFilterView,
CatalogDeclarativeUserDataFilters,
Expand Down Expand Up @@ -418,32 +417,6 @@ def load_and_put_declarative_workspace(self, workspace_id: str, layout_root_path
)
self.put_declarative_workspace(workspace_id=workspace_id, workspace=declarative_workspace)

# AAC (Analytics-as-Code) methods

def load_and_put_aac_workspace(self, workspace_id: str, source_dir: Path) -> None:
"""Load AAC YAML files from source_dir, convert to declarative, and deploy to workspace.

Args:
workspace_id (str):
Workspace identification string e.g. "demo"
source_dir (Path):
Path to the directory containing AAC YAML files.
"""
workspace_model = load_aac_workspace_from_disk(source_dir)
self.put_declarative_workspace(workspace_id=workspace_id, workspace=workspace_model)

def get_and_store_aac_workspace(self, workspace_id: str, source_dir: Path) -> None:
"""Get declarative workspace from server, convert to AAC YAML files, and write to disk.

Args:
workspace_id (str):
Workspace identification string e.g. "demo"
source_dir (Path):
Path to the directory where AAC YAML files will be written.
"""
workspace_model = self.get_declarative_workspace(workspace_id=workspace_id)
store_aac_workspace_to_disk(workspace_model, source_dir)

def clone_workspace(
self,
source_workspace_id: str,
Expand Down
5 changes: 1 addition & 4 deletions packages/gooddata-sdk/src/gooddata_sdk/cli/clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from pathlib import Path

from gooddata_sdk import GoodDataSdk
from gooddata_sdk.catalog.workspace.aac import store_aac_workspace_to_disk
from gooddata_sdk.cli.constants import (
CONFIG_FILE,
DATA_SOURCES,
Expand All @@ -21,9 +20,7 @@
def _clone_workspaces(sdk: GoodDataSdk, path: Path, source_dir: str) -> None:
config_path = path / CONFIG_FILE
workspace_id = _get_workspace_id(config_path)
source_path = path / source_dir
workspace_model = sdk.catalog_workspace.get_declarative_workspace(workspace_id)
store_aac_workspace_to_disk(workspace_model, source_path)
sdk.catalog_workspace.store_declarative_workspace(workspace_id=workspace_id, layout_root_path=path)


@measure_clone(step="data sources")
Expand Down
30 changes: 12 additions & 18 deletions packages/gooddata-sdk/src/gooddata_sdk/cli/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
CatalogDeclarativeWorkspaceDataFilters,
GoodDataSdk,
)
from gooddata_sdk.catalog.workspace.aac import load_aac_workspace_from_disk
from gooddata_sdk.cli.constants import (
CONFIG_FILE,
DATA_SOURCES,
Expand All @@ -19,7 +18,6 @@
WORKSPACES_DATA_FILTERS,
)
from gooddata_sdk.cli.utils import measure_deploy
from gooddata_sdk.config import AacConfig
from gooddata_sdk.utils import read_layout_from_file


Expand All @@ -28,30 +26,26 @@ def _get_workspace_id(config_path: Path, profile: str = "default") -> str:
content = read_layout_from_file(config_path)
if not isinstance(content, dict):
raise ValueError(f"Invalid config file: {config_path}")
config = AacConfig.from_dict(content)
selected_profile = config.default_profile if profile == "default" else profile
if selected_profile not in config.profiles:
raise ValueError(f"Profile '{selected_profile}' not found in config")
p = config.profiles[selected_profile]
if p.workspace_id is None:
profiles = content.get("profiles", {})
default_profile_name = content.get("default_profile", profile)
selected = default_profile_name if profile == "default" else profile
if selected not in profiles:
raise ValueError(f"Profile '{selected}' not found in config")
workspace_id = profiles[selected].get("workspace_id")
if workspace_id is None:
raise ValueError(
f"Profile '{selected_profile}' does not have 'workspace_id' set. Required for deploying workspace objects."
f"Profile '{selected}' does not have 'workspace_id' set. Required for deploying workspace objects."
)
return p.workspace_id
return workspace_id


@measure_deploy(step=WORKSPACES)
def _deploy_workspaces(sdk: GoodDataSdk, path: Path, source_dir: str) -> None:
source_path = path / source_dir
config_path = path / CONFIG_FILE
workspace_id = _get_workspace_id(config_path)
workspace_model = load_aac_workspace_from_disk(source_path)

# Preserve workspace data filters from declarative subdirs
wdf = CatalogDeclarativeWorkspaceDataFilters.load_from_disk(source_path)
if wdf.workspace_data_filters:
workspace_model.remove_wdf_refs()

workspace_model = sdk.catalog_workspace.load_declarative_workspace(
workspace_id=workspace_id, layout_root_path=path
)
sdk.catalog_workspace.put_declarative_workspace(workspace_id=workspace_id, workspace=workspace_model)


Expand Down
9 changes: 4 additions & 5 deletions packages/gooddata-sdk/src/gooddata_sdk/cli/gdc_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from gooddata_sdk.cli.constants import CONFIG_FILE, DEFAULT_SOURCE_DIR
from gooddata_sdk.cli.deploy import deploy_all, deploy_granular
from gooddata_sdk.cli.utils import _SUPPORTED, Bcolors
from gooddata_sdk.config import AacConfig
from gooddata_sdk.utils import read_layout_from_file


Expand All @@ -25,10 +24,10 @@ def _find_config_file() -> Path:
def _get_source_dir(config_path: Path) -> str:
"""Get source_dir from config file, falling back to default."""
content = read_layout_from_file(config_path)
if isinstance(content, dict) and AacConfig.can_structure(content):
config = AacConfig.from_dict(content)
if config.source_dir is not None:
return config.source_dir
if isinstance(content, dict):
source_dir = content.get("source_dir")
if source_dir is not None:
return source_dir
return DEFAULT_SOURCE_DIR


Expand Down
42 changes: 23 additions & 19 deletions packages/gooddata-sdk/src/gooddata_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
import yaml
from cattrs import structure
from cattrs.errors import ClassValidationError
from dotenv import load_dotenv
from gooddata_api_client import ApiAttributeError
from gooddata_api_client.model_utils import OpenApiModel

from gooddata_sdk.compute.model.attribute import Attribute
from gooddata_sdk.compute.model.base import ObjId
from gooddata_sdk.config import AacConfig, Profile
from gooddata_sdk.config import Profile

# Use typing collection types to support python < py3.9
IdObjType = Union[str, ObjId, dict[str, dict[str, str]], dict[str, str]]
Expand Down Expand Up @@ -271,29 +272,27 @@ def _create_profile_legacy(content: dict) -> dict:
raise ValueError(msg)


def _create_profile_aac(profile: str, content: dict) -> dict:
aac_config = AacConfig.from_dict(content)
selected_profile = aac_config.default_profile if profile == "default" else profile
if selected_profile not in aac_config.profiles:
raise ValueError(f"Profile file does not contain the specified profile: {profile}")
return aac_config.profiles[selected_profile].to_dict(use_env=True)


def _get_profile(profile: str, content: dict) -> dict[str, Any]:
is_aac_config = AacConfig.can_structure(content)
if not is_aac_config and profile not in content:
raise ValueError(
"Configuration is invalid. Please check the documentation for the valid configuration: https://www.gooddata.com/docs/python-sdk/latest/getting-started/"
)
if is_aac_config:
return _create_profile_aac(profile, content)
else:
if "profiles" in content and "default_profile" in content:
# New config format: {profiles: {name: {...}}, default_profile: name}
profiles = content["profiles"]
default_profile = content["default_profile"]
selected = default_profile if profile == "default" else profile
if selected not in profiles:
raise ValueError(f"Profile file does not contain the specified profile: {profile}")
return structure(profiles[selected], Profile).to_dict(use_env=True)
elif profile in content:
# Legacy format: {profile_name: {...}}
warn(
"Used configuration is deprecated and will be removed in the future. Please use the new configuration: https://www.gooddata.com/docs/python-sdk/latest/getting-started/",
DeprecationWarning,
stacklevel=2,
)
return _create_profile_legacy(content[profile])
else:
raise ValueError(
"Configuration is invalid. Please check the documentation for the valid configuration: https://www.gooddata.com/docs/python-sdk/latest/getting-started/"
)


def _get_config_content(path: Union[str, Path]) -> Any:
Expand Down Expand Up @@ -331,8 +330,13 @@ def profile_content(profile: str = "default", profiles_path: Path = PROFILES_FIL

def get_ds_credentials(config_file: Union[str, Path]) -> dict[str, str]:
content = _get_config_content(config_file)
config = AacConfig.from_dict(content)
return config.ds_credentials()
if not isinstance(content, dict):
return {}
access = content.get("access")
if not access:
return {}
load_dotenv()
return {k: os.environ.get(v[1:], v) for k, v in access.items()}


def good_pandas_profile_content(
Expand Down
Loading
Loading