Skip to content

Commit 51389a4

Browse files
authored
Merge pull request #1383 from hkad98/jkd/geo
Add GEO_AREA support to gooddata-dbt
2 parents a19bac3 + 3e9d0ac commit 51389a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+377
-350
lines changed

packages/gooddata-dbt/src/gooddata_dbt/dbt/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class GoodDataLabelType(Enum):
2121
HYPERLINK = "HYPERLINK"
2222
GEO_LATITUDE = "GEO_LATITUDE"
2323
GEO_LONGITUDE = "GEO_LONGITUDE"
24+
GEO_AREA = "GEO_AREA"
2425

2526

2627
class GoodDataSortDirection(Enum):

packages/gooddata-dbt/src/gooddata_dbt/dbt/metrics.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# (C) 2023 GoodData Corporation
22
import json
33
import re
4-
from typing import Optional
54

65
import attrs
76
from gooddata_sdk import CatalogDeclarativeMetric, CatalogDeclarativeModel
@@ -26,8 +25,8 @@
2625

2726
@attrs.define(auto_attribs=True, kw_only=True)
2827
class DbtModelMetaGoodDataMetricProps(Base):
29-
model_id: Optional[str] = None
30-
format: Optional[str] = None
28+
model_id: str | None = None
29+
format: str | None = None
3130

3231

3332
@attrs.define(auto_attribs=True, kw_only=True)
@@ -49,11 +48,11 @@ class DbtModelMetric(DbtModelBase):
4948
model: str
5049
calculation_method: str
5150
expression: str
52-
filters: Optional[list[DbtModelMetricFilter]] = None
51+
filters: list[DbtModelMetricFilter] | None = None
5352

5453

5554
class DbtModelMetrics:
56-
def __init__(self, model_ids: Optional[list[str]], ldm: CatalogDeclarativeModel) -> None:
55+
def __init__(self, model_ids: list[str] | None, ldm: CatalogDeclarativeModel) -> None:
5756
self.model_ids = model_ids
5857
self.ldm = ldm
5958
with open(DBT_PATH_TO_MANIFEST) as fp:
@@ -104,7 +103,7 @@ def get_entity_type(self, table_name: str, expression_entity: str) -> str:
104103
else:
105104
raise Exception(f"Unsupported entity type {table_name=} {expression_entity=}")
106105

107-
def make_entity_id(self, table_name: str, token: str) -> Optional[str]:
106+
def make_entity_id(self, table_name: str, token: str) -> str | None:
108107
entity_type = self.get_entity_type(table_name, token)
109108
if not entity_type:
110109
return None
@@ -125,7 +124,7 @@ def resolve_entities_in_expression(self, expression: str, table_name: str) -> st
125124
result_tokens.append(entity_id or token)
126125
return " ".join(result_tokens)
127126

128-
def make_gooddata_filter(self, table_name: str, dbt_filters: Optional[list[DbtModelMetricFilter]] = None) -> str:
127+
def make_gooddata_filter(self, table_name: str, dbt_filters: list[DbtModelMetricFilter] | None = None) -> str:
129128
# TODO - Quite naive implementation
130129
# e.g. missing polishing of values (e.g. SQL vs MAQL enclosers)
131130
gd_maql_filters = []

packages/gooddata-dbt/src/gooddata_dbt/dbt/profiles.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import argparse
33
import os
44
import re
5-
from typing import Optional, Union
5+
from typing import Union
66
from urllib.parse import quote_plus
77

88
import attrs
@@ -97,7 +97,7 @@ class DbtOutputSnowflake(Base):
9797
database: str
9898
warehouse: str
9999
schema: str
100-
query_tag: Optional[str] = None
100+
query_tag: str | None = None
101101

102102
def to_gooddata(self, data_source_id: str, schema_name: str) -> CatalogDataSourceSnowflake:
103103
return CatalogDataSourceSnowflake(
@@ -222,7 +222,7 @@ def inject_env_vars(output_def: dict) -> None:
222222
# else do nothing, real value seems to be stored in dbt profile
223223

224224
@staticmethod
225-
def to_data_class(output: str, output_def: dict) -> Optional[DbtOutput]:
225+
def to_data_class(output: str, output_def: dict) -> DbtOutput | None:
226226
db_type = output_def["type"]
227227
if db_type == "postgres":
228228
return DbtOutputPostgreSQL.from_dict({"name": output, **output_def})

packages/gooddata-dbt/src/gooddata_dbt/dbt/tables.py

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import re
55
from pathlib import Path
6-
from typing import Optional, Union
6+
from typing import Union
77

88
import attrs
99
from gooddata_sdk import CatalogDeclarativeColumn, CatalogDeclarativeTable, CatalogDeclarativeTables
@@ -27,22 +27,23 @@
2727

2828
@attrs.define(auto_attribs=True, kw_only=True)
2929
class DbtModelMetaGoodDataTableProps(Base):
30-
model_id: Optional[str] = None
30+
model_id: str | None = None
3131

3232

3333
@attrs.define(auto_attribs=True, kw_only=True)
3434
class DbtModelMetaGoodDataColumnProps(Base):
35-
id: Optional[str] = None
36-
ldm_type: Optional[GoodDataLdmType] = None
37-
referenced_table: Optional[str] = None
38-
label_type: Optional[GoodDataLabelType] = None
39-
attribute_column: Optional[str] = None
40-
sort_column: Optional[str] = None
41-
sort_direction: Optional[GoodDataSortDirection] = None
42-
default_view: Optional[bool] = None
35+
id: str | None = None
36+
ldm_type: GoodDataLdmType | None = None
37+
referenced_table: str | None = None
38+
label_type: GoodDataLabelType | None = None
39+
attribute_column: str | None = None
40+
sort_column: str | None = None
41+
sort_direction: GoodDataSortDirection | None = None
42+
default_view: bool | None = None
43+
geo_area_config: dict[str, dict[str, str]] | None = None
4344

4445
@property
45-
def gooddata_ref_table_ldm_id(self) -> Optional[str]:
46+
def gooddata_ref_table_ldm_id(self) -> str | None:
4647
if self.referenced_table:
4748
return self.referenced_table.lower()
4849
return None
@@ -73,7 +74,7 @@ class DbtModelBase(Base):
7374
tags: list[str]
7475
# If 2+ references point to the same table, the table plays multiple roles,
7576
# it must be generated as multiple datasets
76-
role_name: Optional[str] = None
77+
role_name: str | None = None
7778

7879
# TODO - duplicate of backend logic.
7980
# Solution: use result of generateLdm as a master template, and override based on dbt metadata only if necessary
@@ -120,7 +121,7 @@ def upper_case_name(self) -> None:
120121

121122
@attrs.define(auto_attribs=True, kw_only=True)
122123
class DbtModelColumn(DbtModelBase):
123-
data_type: Optional[str]
124+
data_type: str | None
124125
meta: DbtModelMetaGoodDataColumn = attrs.field(factory=DbtModelMetaGoodDataColumn)
125126

126127
# Enable to override LDM ID for LDM entities derived from columns (attributes, ...)
@@ -130,21 +131,21 @@ def ldm_id(self) -> str:
130131
return self.meta.gooddata.id or self.gooddata_ldm_id
131132

132133
@property
133-
def ldm_type(self) -> Optional[str]:
134+
def ldm_type(self) -> str | None:
134135
if self.meta.gooddata.ldm_type is None:
135136
return None
136137
else:
137138
return self.meta.gooddata.ldm_type.value
138139

139140
@property
140-
def label_type(self) -> Optional[str]:
141+
def label_type(self) -> str | None:
141142
if self.meta.gooddata.label_type is None:
142143
return None
143144
else:
144145
return self.meta.gooddata.label_type.value
145146

146147
@property
147-
def sort_direction(self) -> Optional[str]:
148+
def sort_direction(self) -> str | None:
148149
if self.meta.gooddata.sort_direction is None:
149150
return None
150151
else:
@@ -387,22 +388,23 @@ def make_facts(table: DbtModelTable) -> list[dict]:
387388
return facts
388389

389390
@staticmethod
390-
def make_labels(table: DbtModelTable, attribute_column: DbtModelColumn) -> tuple[list[dict], Optional[dict]]:
391+
def make_labels(table: DbtModelTable, attribute_column: DbtModelColumn) -> tuple[list[dict], dict | None]:
391392
labels = []
392393
default_view = None
393394
for column in table.columns.values():
394395
if column.gooddata_is_label(attribute_column.name):
395-
labels.append(
396-
{
397-
"id": column.ldm_id,
398-
"title": column.gooddata_ldm_title,
399-
"description": column.gooddata_ldm_description,
400-
"source_column": column.name,
401-
"source_column_data_type": column.data_type,
402-
"value_type": column.label_type,
403-
"tags": [table.gooddata_ldm_title] + column.tags,
404-
}
405-
)
396+
label_dict: dict = {
397+
"id": column.ldm_id,
398+
"title": column.gooddata_ldm_title,
399+
"description": column.gooddata_ldm_description,
400+
"source_column": column.name,
401+
"source_column_data_type": column.data_type,
402+
"value_type": column.label_type,
403+
"tags": [table.gooddata_ldm_title] + column.tags,
404+
}
405+
if column.meta.gooddata.geo_area_config is not None:
406+
label_dict["geo_area_config"] = column.meta.gooddata.geo_area_config
407+
labels.append(label_dict)
406408
if column.meta.gooddata.default_view:
407409
default_view = {
408410
"id": column.ldm_id,
@@ -507,7 +509,7 @@ def populate_role_playing_tables(tables: list[DbtModelTable], role_playing_table
507509
result.append(table)
508510
return result
509511

510-
def make_declarative_datasets(self, data_source_id: str, model_ids: Optional[list[str]]) -> dict:
512+
def make_declarative_datasets(self, data_source_id: str, model_ids: list[str] | None) -> dict:
511513
result: dict[str, list] = {"datasets": [], "date_instances": []}
512514
model_tables = [t for t in self.tables if not model_ids or t.meta.gooddata.model_id in model_ids]
513515
role_playing_tables = self.find_role_playing_tables(model_tables)
@@ -517,7 +519,7 @@ def make_declarative_datasets(self, data_source_id: str, model_ids: Optional[lis
517519
result = self.make_dataset(data_source_id, table, role_playing_tables, result)
518520
return result
519521

520-
def get_entity_type(self, table_name: str, column_name: str) -> Optional[str]:
522+
def get_entity_type(self, table_name: str, column_name: str) -> str | None:
521523
comp_table_name = table_name
522524
if self.upper_case:
523525
comp_table_name = table_name.upper()

packages/gooddata-dbt/src/gooddata_dbt/dbt_plugin.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from asyncio import Semaphore
66
from pathlib import Path
77
from time import time
8-
from typing import Optional
98

109
import tabulate
1110
import yaml
@@ -36,7 +35,7 @@ def generate_and_put_ldm(
3635
data_source_id: str,
3736
workspace_id: str,
3837
dbt_tables: DbtModelTables,
39-
model_ids: Optional[list[str]],
38+
model_ids: list[str] | None,
4039
) -> None:
4140
scan_request = CatalogScanModelRequest(scan_tables=True, scan_views=True)
4241
logger.info(f"Scan data source {data_source_id=}")
@@ -68,7 +67,7 @@ def deploy_ldm(
6867
args: Namespace,
6968
all_model_ids: list[str],
7069
sdk_wrapper: GoodDataSdkWrapper,
71-
model_ids: Optional[list[str]],
70+
model_ids: list[str] | None,
7271
workspace_id: str,
7372
) -> None:
7473
logger.info("Generate and put LDM")
@@ -186,7 +185,7 @@ async def test_visualizations(
186185
logger: logging.Logger,
187186
sdk_wrapper: GoodDataSdkWrapper,
188187
workspace_id: str,
189-
skip_tests: Optional[list[str]],
188+
skip_tests: list[str] | None,
190189
test_visualizations_parallelism: int = 1,
191190
) -> None:
192191
start = time()
@@ -334,7 +333,7 @@ def process_organization(
334333
logger: logging.Logger,
335334
sdk_wrapper: GoodDataSdkWrapper,
336335
gd_config: GoodDataConfig,
337-
organization: Optional[GoodDataConfigOrganization] = None,
336+
organization: GoodDataConfigOrganization | None = None,
338337
) -> None:
339338
if args.method == "upload_notification":
340339
dbt_profiles = DbtProfiles(args)

packages/gooddata-dbt/src/gooddata_dbt/gooddata/config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# (C) 2023 GoodData Corporation
2-
from typing import Optional
32

43
import attr
54
import attrs
@@ -37,8 +36,8 @@ class GoodDataConfigProduct(Base):
3736
name: str
3837
environment_setup_id: str
3938
model_ids: list[str] = attr.field(factory=list)
40-
localization: Optional[GoodDataConfigLocalization] = None
41-
skip_tests: Optional[list[str]] = None
39+
localization: GoodDataConfigLocalization | None = None
40+
skip_tests: list[str] | None = None
4241

4342

4443
@attrs.define(auto_attribs=True, kw_only=True)
@@ -49,7 +48,7 @@ class GoodDataConfigOrganization(Base):
4948

5049
@attrs.define(auto_attribs=True, kw_only=True)
5150
class GoodDataGlobalConfig(Base):
52-
test_visualizations_parallelism: Optional[int] = 1
51+
test_visualizations_parallelism: int | None = 1
5352

5453

5554
@attrs.define(auto_attribs=True, kw_only=True)

packages/gooddata-dbt/src/gooddata_dbt/sdk_wrapper.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# (C) 2023 GoodData Corporation
22
import argparse
33
from logging import Logger
4-
from typing import Optional
54

65
from gooddata_sdk import GoodDataSdk
76

@@ -11,7 +10,7 @@
1110
class GoodDataSdkWrapper:
1211
# Timeout=600 because supporting waiting for GoodData services to start
1312
def __init__(
14-
self, args: argparse.Namespace, logger: Logger, profile: Optional[str] = None, timeout: int = 600
13+
self, args: argparse.Namespace, logger: Logger, profile: str | None = None, timeout: int = 600
1514
) -> None:
1615
self.args = args
1716
self.logger = logger
@@ -22,7 +21,7 @@ def __init__(
2221
if not self.args.dry_run:
2322
self.wait_for_gooddata_is_up(self.timeout)
2423

25-
def get_host_from_sdk(self) -> Optional[str]:
24+
def get_host_from_sdk(self) -> str | None:
2625
# TODO - make _hostname public in gooddata_sdk
2726
return self.sdk.client._hostname
2827

@@ -54,7 +53,7 @@ def wait_for_gooddata_is_up(self, timeout: int) -> None:
5453
self.sdk.support.wait_till_available(timeout=timeout)
5554
self.logger.info(f"Host {host} is up")
5655

57-
def pre_cache_visualizations(self, workspaces: Optional[list] = None) -> None:
56+
def pre_cache_visualizations(self, workspaces: list | None = None) -> None:
5857
if not workspaces:
5958
workspaces = [w.id for w in self.sdk.catalog_workspace.list_workspaces()]
6059
for workspace_id in workspaces:

packages/gooddata-dbt/tests/resources/dbt_target/manifest.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,25 @@
18601860
"data_type": null,
18611861
"quote": null,
18621862
"tags": []
1863+
},
1864+
"country": {
1865+
"name": "country",
1866+
"description": "",
1867+
"meta": {
1868+
"gooddata": {
1869+
"ldm_type": "label",
1870+
"label_type": "GEO_AREA",
1871+
"attribute_column": "code",
1872+
"geo_area_config": {
1873+
"collection": {
1874+
"id": "countries"
1875+
}
1876+
}
1877+
}
1878+
},
1879+
"data_type": null,
1880+
"quote": null,
1881+
"tags": []
18631882
}
18641883
},
18651884
"meta": {

packages/gooddata-dbt/tests/resources/gooddata_layouts/pdm/airports.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ columns:
2121
- dataType: STRING
2222
isPrimaryKey: false
2323
name: name
24+
- dataType: STRING
25+
isPrimaryKey: false
26+
name: country
2427
- dataType: STRING
2528
isPrimaryKey: false
2629
name: state

0 commit comments

Comments
 (0)