Skip to content

Commit 64939b0

Browse files
committed
NAS-3782 reimplemented declarative classes with attrs and cattrs
* classes uses attrs and cattrs which should make development easier in the future * alias @attr.s was used because IntelliJ supports hinting for that when it supports new aliases such as define, the new aliases will be used * unfortunately annotations (list, dict,...) from __future__ cannot be used, because they are not supported by combination of cattrs and python3.7 and python3.8
1 parent ef1bccd commit 64939b0

18 files changed

Lines changed: 490 additions & 1210 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# (C) 2022 GoodData Corporation
2+
from __future__ import annotations
3+
4+
from typing import Any, Dict, Type, TypeVar
5+
6+
import attr
7+
from cattrs import structure
8+
9+
T = TypeVar("T", bound="Base")
10+
11+
12+
@attr.s
13+
class Base:
14+
@classmethod
15+
def from_api(cls: Type[T], entity: Dict[str, Any]) -> T:
16+
"""
17+
Creates object from entity passed by client class, which represents it as dictionary.
18+
"""
19+
return structure(entity, cls)
20+
21+
@classmethod
22+
def from_dict(cls: Type[T], data: Dict[str, Any], camel_case: bool = True) -> T:
23+
"""
24+
Creates object from dictionary. It needs to be specified if the dictionary is in camelCase or snake_case.
25+
"""
26+
# This branch does not have to exist, but in case of camel_case=False it can perform fewer calls
27+
if not camel_case:
28+
return structure(data, cls)
29+
client_object = cls.client_class().from_dict(data, camel_case)
30+
return cls.from_api(client_object)
31+
32+
def to_dict(self, camel_case: bool = True) -> Dict[str, Any]:
33+
"""
34+
Converts object into dictionary. Optional argument if the dictionary should be camelCase or snake_case can be
35+
specified.
36+
"""
37+
# This branch does not have to exist, but in case of camel_case=False it can perform fewer calls
38+
if not camel_case:
39+
return self._get_snake_dict()
40+
return self.to_api().to_dict(camel_case)
41+
42+
@staticmethod
43+
def _is_attribute_private(attribute: attr.Attribute) -> bool:
44+
return attribute.name.startswith("_")
45+
46+
def _get_snake_dict(self) -> Dict[str, Any]:
47+
return attr.asdict(
48+
self, filter=lambda attribute, value: value is not None and not self._is_attribute_private(attribute)
49+
)
50+
51+
@staticmethod
52+
def client_class() -> Any:
53+
return NotImplemented
54+
55+
def to_api(self) -> Any:
56+
dictionary = self._get_snake_dict()
57+
return self.client_class().from_dict(dictionary, camel_case=False)
Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,30 @@
11
# (C) 2022 GoodData Corporation
22
from __future__ import annotations
33

4-
from typing import Any
4+
from typing import Optional, Type
5+
6+
import attr
57

68
from gooddata_metadata_client.model.generate_ldm_request import GenerateLdmRequest
9+
from gooddata_sdk.catalog.base import Base
710

811

9-
class CatalogGenerateLdmRequest:
10-
def __init__(
11-
self,
12-
separator: str,
13-
generate_long_ids: bool = None,
14-
table_prefix: str = None,
15-
view_prefix: str = None,
16-
primary_label_prefix: str = None,
17-
secondary_label_prefix: str = None,
18-
fact_prefix: str = None,
19-
date_granularities: str = None,
20-
grain_prefix: str = None,
21-
reference_prefix: str = None,
22-
grain_reference_prefix: str = None,
23-
denorm_prefix: str = None,
24-
wdf_prefix: str = None,
25-
):
26-
self.separator = separator
27-
self.generate_long_ids = generate_long_ids
28-
self.table_prefix = table_prefix
29-
self.view_prefix = view_prefix
30-
self.primary_label_prefix = primary_label_prefix
31-
self.secondary_label_prefix = secondary_label_prefix
32-
self.fact_prefix = fact_prefix
33-
self.date_granularities = date_granularities
34-
self.grain_prefix = grain_prefix
35-
self.reference_prefix = reference_prefix
36-
self.grain_reference_prefix = grain_reference_prefix
37-
self.denorm_prefix = denorm_prefix
38-
self.wdf_prefix = wdf_prefix
12+
@attr.s(auto_attribs=True, kw_only=True)
13+
class CatalogGenerateLdmRequest(Base):
14+
separator: str = "__"
15+
generate_long_ids: Optional[bool] = None
16+
table_prefix: Optional[str] = None
17+
view_prefix: Optional[str] = None
18+
primary_label_prefix: Optional[str] = None
19+
secondary_label_prefix: Optional[str] = None
20+
fact_prefix: Optional[str] = None
21+
date_granularities: Optional[str] = None
22+
grain_prefix: Optional[str] = None
23+
reference_prefix: Optional[str] = None
24+
grain_reference_prefix: Optional[str] = None
25+
denorm_prefix: Optional[str] = None
26+
wdf_prefix: Optional[str] = None
3927

40-
def to_api(self) -> GenerateLdmRequest:
41-
kwargs: dict[str, Any] = dict()
42-
if self.generate_long_ids:
43-
kwargs["generate_long_ids"] = self.generate_long_ids
44-
if self.table_prefix:
45-
kwargs["table_prefix"] = self.table_prefix
46-
if self.view_prefix:
47-
kwargs["view_prefix"] = self.view_prefix
48-
if self.primary_label_prefix:
49-
kwargs["primary_label_prefix"] = self.primary_label_prefix
50-
if self.secondary_label_prefix:
51-
kwargs["secondary_label_prefix"] = self.secondary_label_prefix
52-
if self.fact_prefix:
53-
kwargs["fact_prefix"] = self.fact_prefix
54-
if self.date_granularities:
55-
kwargs["date_granularities"] = self.date_granularities
56-
if self.grain_prefix:
57-
kwargs["grain_prefix"] = self.grain_prefix
58-
if self.reference_prefix:
59-
kwargs["reference_prefix"] = self.reference_prefix
60-
if self.grain_reference_prefix:
61-
kwargs["grain_reference_prefix"] = self.grain_reference_prefix
62-
if self.denorm_prefix:
63-
kwargs["denorm_prefix"] = self.denorm_prefix
64-
if self.wdf_prefix:
65-
kwargs["wdf_prefix"] = self.wdf_prefix
66-
return GenerateLdmRequest(self.separator, **kwargs)
28+
@staticmethod
29+
def client_class() -> Type[GenerateLdmRequest]:
30+
return GenerateLdmRequest
Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,28 @@
11
# (C) 2022 GoodData Corporation
22
from __future__ import annotations
33

4-
from typing import Any
4+
from typing import Any, Optional, Type
5+
6+
import attr
7+
from attr import field
58

69
from gooddata_scan_client.model.scan_request import ScanRequest
10+
from gooddata_sdk.catalog.base import Base
11+
12+
13+
def one_scan_true(instance: CatalogScanModelRequest, *args: Any) -> None:
14+
if not instance.scan_views and not instance.scan_tables:
15+
raise ValueError("Either scan_tables or scan_views must be True in CatalogScanModelRequest.")
16+
717

18+
@attr.s(auto_attribs=True, kw_only=True)
19+
class CatalogScanModelRequest(Base):
20+
separator: str = "__"
21+
scan_tables: bool = field(default=True, validator=one_scan_true)
22+
scan_views: bool = field(default=False, validator=one_scan_true)
23+
table_prefix: Optional[str] = None
24+
view_prefix: Optional[str] = None
825

9-
class CatalogScanModelRequest:
10-
def __init__(
11-
self,
12-
separator: str = "__",
13-
scan_tables: bool = True,
14-
scan_views: bool = False,
15-
table_prefix: str = None,
16-
view_prefix: str = None,
17-
):
18-
self.separator = separator
19-
self.scan_tables = scan_tables
20-
self.scan_views = scan_views
21-
self.table_prefix = table_prefix
22-
self.view_prefix = view_prefix
23-
if not scan_views and not scan_tables:
24-
raise ValueError("Either scan_tables or scan_views must be True in CatalogScanModelRequest.")
25-
26-
def to_api(self) -> ScanRequest:
27-
kwargs: dict[str, Any] = dict()
28-
if self.table_prefix:
29-
kwargs["table_prefix"] = self.table_prefix
30-
if self.view_prefix:
31-
kwargs["view_prefix"] = self.view_prefix
32-
return ScanRequest(separator=self.separator, scan_tables=self.scan_tables, scan_views=self.scan_views, **kwargs)
26+
@staticmethod
27+
def client_class() -> Type[ScanRequest]:
28+
return ScanRequest

0 commit comments

Comments
 (0)