Skip to content

Commit f7f1ace

Browse files
committed
NAS-4254 place CatalogWorkspaceContentService to separate file
1 parent 12c8ed2 commit f7f1ace

2 files changed

Lines changed: 247 additions & 217 deletions

File tree

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# (C) 2022 GoodData Corporation
2+
from __future__ import annotations
3+
4+
import functools
5+
from pathlib import Path
6+
from typing import List, Optional, Union
7+
8+
import gooddata_afm_client.apis as afm_apis
9+
import gooddata_afm_client.models as afm_models
10+
from gooddata_sdk.catalog.catalog_service_base import CatalogServiceBase
11+
from gooddata_sdk.catalog.data_source.validation.data_source import DataSourceValidator
12+
from gooddata_sdk.catalog.types import ValidObjects
13+
from gooddata_sdk.catalog.workspace.declarative_model.workspace.analytics_model.analytics_model import (
14+
CatalogDeclarativeAnalytics,
15+
)
16+
from gooddata_sdk.catalog.workspace.declarative_model.workspace.logical_model.ldm import CatalogDeclarativeModel
17+
from gooddata_sdk.catalog.workspace.declarative_model.workspace.workspace import LAYOUT_WORKSPACES_DIR
18+
from gooddata_sdk.catalog.workspace.entity_model.content_objects.dataset import (
19+
CatalogAttribute,
20+
CatalogFact,
21+
CatalogLabel,
22+
)
23+
from gooddata_sdk.catalog.workspace.entity_model.content_objects.metric import CatalogMetric
24+
from gooddata_sdk.catalog.workspace.entity_model.graph_objects.graph import (
25+
CatalogDependentEntitiesRequest,
26+
CatalogDependentEntitiesResponse,
27+
)
28+
from gooddata_sdk.catalog.workspace.model_container import CatalogWorkspaceContent
29+
from gooddata_sdk.client import GoodDataApiClient
30+
from gooddata_sdk.compute.model.attribute import Attribute
31+
from gooddata_sdk.compute.model.execution import ExecutionDefinition, compute_model_to_api_model
32+
from gooddata_sdk.compute.model.filter import Filter
33+
from gooddata_sdk.compute.model.metric import Metric
34+
from gooddata_sdk.utils import load_all_entities
35+
36+
ValidObjectTypes = Union[Attribute, Metric, Filter, CatalogLabel, CatalogFact, CatalogMetric]
37+
38+
# Use typing collection types to support python < py3.9
39+
ValidObjectsInputType = Union[ValidObjectTypes, List[ValidObjectTypes], ExecutionDefinition]
40+
41+
42+
class CatalogWorkspaceContentService(CatalogServiceBase):
43+
# Note on the disabled checking:
44+
# generated client has issues parsing the vis objects; .. have to avoid return type checks
45+
#
46+
# note: the parsing is done lazily so it does not necessarily bomb on the next line but when trying to
47+
# access returned object's properties
48+
49+
def __init__(self, api_client: GoodDataApiClient) -> None:
50+
super(CatalogWorkspaceContentService, self).__init__(api_client)
51+
self._afm_actions_api = afm_apis.ActionsApi(api_client.afm_client)
52+
53+
def get_attributes_catalog(self, workspace_id: str) -> list[CatalogAttribute]:
54+
get_attributes = functools.partial(
55+
self._entities_api.get_all_entities_attributes,
56+
workspace_id,
57+
_check_return_type=False,
58+
)
59+
attributes = load_all_entities(get_attributes)
60+
# Empty labels list is set. It will be changed in the future.
61+
catalog_attributes = [CatalogAttribute(attribute, []) for attribute in attributes.data]
62+
return catalog_attributes
63+
64+
def get_labels_catalog(self, workspace_id: str) -> list[CatalogLabel]:
65+
get_labels = functools.partial(
66+
self._entities_api.get_all_entities_labels,
67+
workspace_id,
68+
_check_return_type=False,
69+
)
70+
labels = load_all_entities(get_labels)
71+
catalog_labels = [CatalogLabel(label) for label in labels.data]
72+
return catalog_labels
73+
74+
def get_metrics_catalog(self, workspace_id: str) -> list[CatalogMetric]:
75+
get_metrics = functools.partial(
76+
self._entities_api.get_all_entities_metrics, workspace_id, _check_return_type=False
77+
)
78+
metrics = load_all_entities(get_metrics)
79+
catalog_metrics = [CatalogMetric(metric) for metric in metrics.data]
80+
return catalog_metrics
81+
82+
def get_facts_catalog(self, workspace_id: str) -> list[CatalogFact]:
83+
get_facts = functools.partial(self._entities_api.get_all_entities_facts, workspace_id, _check_return_type=False)
84+
facts = load_all_entities(get_facts)
85+
catalog_facts = [CatalogFact(fact) for fact in facts.data]
86+
return catalog_facts
87+
88+
def get_full_catalog(self, workspace_id: str) -> CatalogWorkspaceContent:
89+
"""
90+
Retrieves catalog for a workspace. Catalog contains all data sets and metrics defined in that workspace.
91+
92+
:param workspace_id: workspace identifier
93+
"""
94+
get_datasets = functools.partial(
95+
self._entities_api.get_all_entities_datasets,
96+
workspace_id,
97+
include=["attributes", "facts"],
98+
_check_return_type=False,
99+
)
100+
101+
get_attributes = functools.partial(
102+
self._entities_api.get_all_entities_attributes,
103+
workspace_id,
104+
include=["labels"],
105+
_check_return_type=False,
106+
)
107+
108+
get_metrics = functools.partial(
109+
self._entities_api.get_all_entities_metrics, workspace_id, _check_return_type=False
110+
)
111+
112+
attributes = load_all_entities(get_attributes)
113+
datasets = load_all_entities(get_datasets)
114+
metrics = load_all_entities(get_metrics)
115+
116+
valid_obj_fun = functools.partial(self.compute_valid_objects, workspace_id)
117+
118+
return CatalogWorkspaceContent.create_workspace_content_catalog(valid_obj_fun, datasets, attributes, metrics)
119+
120+
@staticmethod
121+
def _prepare_afm_for_availability(items: list[ValidObjectTypes]) -> afm_models.AFM:
122+
attributes = []
123+
metrics = []
124+
filters = []
125+
126+
for item in items:
127+
if isinstance(item, Attribute):
128+
attributes.append(item)
129+
elif isinstance(item, Metric):
130+
metrics.append(item)
131+
elif isinstance(item, Filter):
132+
filters.append(item)
133+
elif isinstance(item, CatalogLabel):
134+
attributes.append(item.as_computable())
135+
elif isinstance(item, (CatalogFact, CatalogMetric)):
136+
metrics.append(item.as_computable())
137+
138+
return compute_model_to_api_model(attributes=attributes, metrics=metrics, filters=filters)
139+
140+
def compute_valid_objects(self, workspace_id: str, ctx: ValidObjectsInputType) -> ValidObjects:
141+
"""
142+
Returns attributes, facts, and metrics which are valid to add to a context that already
143+
contains some entities from the semantic model. The entities are typically used to compute analytics and
144+
come from the execution definition. You may, however, specify the entities through different layers of
145+
convenience.
146+
147+
:param workspace_id: workspace identifier
148+
:param ctx: items already in context. you can specify context in one of the following ways:
149+
150+
- single item or list of items from the execution model
151+
- single item or list of items from catalog model; catalog fact, label or metric may be added
152+
- the entire execution definition that is used to compute analytics
153+
154+
:return: a dict of sets; type of available object is used as key in the dict,
155+
the value is a set containing id's of available items
156+
"""
157+
if isinstance(ctx, ExecutionDefinition):
158+
afm = compute_model_to_api_model(attributes=ctx.attributes, metrics=ctx.metrics, filters=ctx.filters)
159+
else:
160+
_ctx = ctx if isinstance(ctx, list) else [ctx]
161+
afm = self._prepare_afm_for_availability(_ctx)
162+
163+
query = afm_models.AfmValidObjectsQuery(afm=afm, types=["facts", "attributes", "measures"])
164+
response = self._afm_actions_api.compute_valid_objects(workspace_id=workspace_id, afm_valid_objects_query=query)
165+
166+
by_type: dict[str, set[str]] = dict()
167+
168+
for available in response.items:
169+
_type = available["type"]
170+
171+
if _type not in by_type:
172+
items_of_type: set[str] = set()
173+
by_type[_type] = items_of_type
174+
else:
175+
items_of_type = by_type[_type]
176+
177+
items_of_type.add(available["id"])
178+
179+
return by_type
180+
181+
def get_dependent_entities_graph(self, workspace_id: str) -> CatalogDependentEntitiesResponse:
182+
return CatalogDependentEntitiesResponse.from_api(
183+
self._metadata_actions_api.get_dependent_entities_graph(workspace_id=workspace_id)
184+
)
185+
186+
def get_dependent_entities_graph_from_entry_points(
187+
self, workspace_id: str, dependent_entities_request: CatalogDependentEntitiesRequest
188+
) -> CatalogDependentEntitiesResponse:
189+
return CatalogDependentEntitiesResponse.from_api(
190+
self._metadata_actions_api.get_dependent_entities_graph_from_entry_points(
191+
workspace_id=workspace_id, dependent_entities_request=dependent_entities_request.to_api()
192+
)
193+
)
194+
195+
# Declarative methods for workspace content service are listed below
196+
197+
def get_declarative_ldm(self, workspace_id: str) -> CatalogDeclarativeModel:
198+
return CatalogDeclarativeModel.from_api(self._layout_api.get_logical_model(workspace_id))
199+
200+
def store_declarative_ldm(self, workspace_id: str, layout_root_path: Path = Path.cwd()) -> None:
201+
workspace_folder = self.layout_workspace_folder(workspace_id, layout_root_path)
202+
self.get_declarative_ldm(workspace_id).store_to_disk(workspace_folder)
203+
204+
def layout_workspace_folder(self, workspace_id: str, layout_root_path: Path) -> Path:
205+
return self.layout_organization_folder(layout_root_path) / LAYOUT_WORKSPACES_DIR / workspace_id
206+
207+
def load_declarative_ldm(self, workspace_id: str, layout_root_path: Path = Path.cwd()) -> CatalogDeclarativeModel:
208+
workspace_folder = self.layout_workspace_folder(workspace_id, layout_root_path)
209+
return CatalogDeclarativeModel.load_from_disk(workspace_folder)
210+
211+
def load_and_put_declarative_ldm(
212+
self,
213+
workspace_id: str,
214+
layout_root_path: Path = Path.cwd(),
215+
validator: Optional[DataSourceValidator] = None,
216+
) -> None:
217+
declarative_ldm = self.load_declarative_ldm(workspace_id, layout_root_path)
218+
self.put_declarative_ldm(workspace_id, declarative_ldm, validator)
219+
220+
def put_declarative_ldm(
221+
self, workspace_id: str, ldm: CatalogDeclarativeModel, validator: Optional[DataSourceValidator] = None
222+
) -> None:
223+
if validator is not None:
224+
validator.validate_ldm(ldm)
225+
self._layout_api.set_logical_model(workspace_id, ldm.to_api())
226+
227+
def get_declarative_analytics_model(self, workspace_id: str) -> CatalogDeclarativeAnalytics:
228+
return CatalogDeclarativeAnalytics.from_api(self._layout_api.get_analytics_model(workspace_id))
229+
230+
def put_declarative_analytics_model(self, workspace_id: str, analytics_model: CatalogDeclarativeAnalytics) -> None:
231+
self._layout_api.set_analytics_model(workspace_id, analytics_model.to_api())
232+
233+
def store_declarative_analytics_model(self, workspace_id: str, layout_root_path: Path = Path.cwd()) -> None:
234+
workspace_folder = self.layout_workspace_folder(workspace_id, layout_root_path)
235+
declarative_analytics_model = self.get_declarative_analytics_model(workspace_id)
236+
declarative_analytics_model.store_to_disk(workspace_folder)
237+
238+
def load_declarative_analytics_model(
239+
self, workspace_id: str, layout_root_path: Path = Path.cwd()
240+
) -> CatalogDeclarativeAnalytics:
241+
workspace_folder = self.layout_workspace_folder(workspace_id, layout_root_path)
242+
return CatalogDeclarativeAnalytics.load_from_disk(workspace_folder)
243+
244+
def load_and_put_declarative_analytics_model(self, workspace_id: str, layout_root_path: Path = Path.cwd()) -> None:
245+
declarative_analytics_model = self.load_declarative_analytics_model(workspace_id, layout_root_path)
246+
self.put_declarative_analytics_model(workspace_id, declarative_analytics_model)

0 commit comments

Comments
 (0)