gooddata-dbt 1.60.1.dev1__tar.gz → 1.60.1.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/PKG-INFO +2 -2
  2. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/pyproject.toml +11 -2
  3. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/base.py +1 -0
  4. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/metrics.py +6 -7
  5. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/profiles.py +3 -3
  6. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/tables.py +32 -30
  7. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt_plugin.py +4 -5
  8. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/gooddata/config.py +3 -4
  9. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/sdk_wrapper.py +3 -4
  10. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/dbt_target/manifest.json +19 -0
  11. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/airports.yaml +3 -0
  12. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/test_tables.py +32 -0
  13. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tox.ini +1 -1
  14. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/.env.custom.dev +0 -0
  15. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/.env.dev +0 -0
  16. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/.gitignore +0 -0
  17. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/LICENSE.txt +0 -0
  18. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/Makefile +0 -0
  19. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/README.md +0 -0
  20. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/bin/gooddata-dbt +0 -0
  21. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/gooddata_example.yml +0 -0
  22. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/__init__.py +0 -0
  23. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/_version.py +0 -0
  24. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/args.py +0 -0
  25. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/__init__.py +0 -0
  26. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/cloud.py +0 -0
  27. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/dbt/environment.py +0 -0
  28. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/gooddata/__init__.py +0 -0
  29. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/gooddata/api_wrapper.py +0 -0
  30. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/logger.py +0 -0
  31. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/main.py +0 -0
  32. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/src/gooddata_dbt/utils.py +0 -0
  33. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/conftest.py +0 -0
  34. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/gooddata_example.yml +0 -0
  35. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/dbt_profiles/profiles.yml +0 -0
  36. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/aircraft.yaml +0 -0
  37. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/aircraft_models.yaml +0 -0
  38. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/ambient_temperature.yaml +0 -0
  39. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/carriers.yaml +0 -0
  40. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/census.yaml +0 -0
  41. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/census_by_country.yaml +0 -0
  42. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/commits.yaml +0 -0
  43. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/customer.yaml +0 -0
  44. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/eshop_customers.yaml +0 -0
  45. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/flights.yaml +0 -0
  46. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/home_rentals.yaml +0 -0
  47. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/house_property_sales_time_series.yaml +0 -0
  48. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/mall_customers.yaml +0 -0
  49. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/monthlyinventory.yaml +0 -0
  50. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/order_lines.yaml +0 -0
  51. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/orders.yaml +0 -0
  52. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/product.yaml +0 -0
  53. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/pull_requests.yaml +0 -0
  54. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/repos.yaml +0 -0
  55. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/returns.yaml +0 -0
  56. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/telco_customer_churn.yaml +0 -0
  57. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/users.yaml +0 -0
  58. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/resources/gooddata_layouts/pdm/workflow_runs.yaml +0 -0
  59. {gooddata_dbt-1.60.1.dev1 → gooddata_dbt-1.60.1.dev2}/tests/test_profiles.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gooddata-dbt
3
- Version: 1.60.1.dev1
3
+ Version: 1.60.1.dev2
4
4
  Summary: dbt plugin for GoodData
5
5
  Author-email: GoodData <support@gooddata.com>
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: attrs<=24.2.0,>=21.4.0
21
21
  Requires-Dist: cattrs<=24.1.1,>=22.1.0
22
- Requires-Dist: gooddata-sdk~=1.60.1.dev1
22
+ Requires-Dist: gooddata-sdk~=1.60.1.dev2
23
23
  Requires-Dist: pyyaml>=6.0
24
24
  Requires-Dist: requests~=2.32.0
25
25
  Requires-Dist: tabulate~=0.8.10
@@ -1,7 +1,7 @@
1
1
  # (C) 2025 GoodData Corporation
2
2
  [project]
3
3
  name = "gooddata-dbt"
4
- version = "1.60.1.dev1"
4
+ version = "1.60.1.dev2"
5
5
  description = "dbt plugin for GoodData"
6
6
  readme = "README.md"
7
7
  license = "MIT"
@@ -10,7 +10,7 @@ authors = [
10
10
  ]
11
11
  requires-python = ">=3.10"
12
12
  dependencies = [
13
- "gooddata-sdk~=1.60.1.dev1",
13
+ "gooddata-sdk~=1.60.1.dev2",
14
14
  "pyyaml>=6.0",
15
15
  "attrs>=21.4.0,<=24.2.0",
16
16
  "cattrs>=22.1.0,<=24.1.1",
@@ -47,6 +47,15 @@ allowed-unresolved-imports = ["deep_translator", "github"]
47
47
  [tool.hatch.build.targets.wheel]
48
48
  packages = ["src/gooddata_dbt"]
49
49
 
50
+ [tool.coverage.run]
51
+ source = ["gooddata_dbt"]
52
+
53
+ [tool.coverage.paths]
54
+ source = [
55
+ "src/gooddata_dbt",
56
+ "**/site-packages/gooddata_dbt",
57
+ ]
58
+
50
59
  [build-system]
51
60
  requires = ["hatchling"]
52
61
  build-backend = "hatchling.build"
@@ -21,6 +21,7 @@ class GoodDataLabelType(Enum):
21
21
  HYPERLINK = "HYPERLINK"
22
22
  GEO_LATITUDE = "GEO_LATITUDE"
23
23
  GEO_LONGITUDE = "GEO_LONGITUDE"
24
+ GEO_AREA = "GEO_AREA"
24
25
 
25
26
 
26
27
  class GoodDataSortDirection(Enum):
@@ -1,7 +1,6 @@
1
1
  # (C) 2023 GoodData Corporation
2
2
  import json
3
3
  import re
4
- from typing import Optional
5
4
 
6
5
  import attrs
7
6
  from gooddata_sdk import CatalogDeclarativeMetric, CatalogDeclarativeModel
@@ -26,8 +25,8 @@ DBT_TO_GD_FILTER_OPERATORS = {
26
25
 
27
26
  @attrs.define(auto_attribs=True, kw_only=True)
28
27
  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
31
30
 
32
31
 
33
32
  @attrs.define(auto_attribs=True, kw_only=True)
@@ -49,11 +48,11 @@ class DbtModelMetric(DbtModelBase):
49
48
  model: str
50
49
  calculation_method: str
51
50
  expression: str
52
- filters: Optional[list[DbtModelMetricFilter]] = None
51
+ filters: list[DbtModelMetricFilter] | None = None
53
52
 
54
53
 
55
54
  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:
57
56
  self.model_ids = model_ids
58
57
  self.ldm = ldm
59
58
  with open(DBT_PATH_TO_MANIFEST) as fp:
@@ -104,7 +103,7 @@ class DbtModelMetrics:
104
103
  else:
105
104
  raise Exception(f"Unsupported entity type {table_name=} {expression_entity=}")
106
105
 
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:
108
107
  entity_type = self.get_entity_type(table_name, token)
109
108
  if not entity_type:
110
109
  return None
@@ -125,7 +124,7 @@ class DbtModelMetrics:
125
124
  result_tokens.append(entity_id or token)
126
125
  return " ".join(result_tokens)
127
126
 
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:
129
128
  # TODO - Quite naive implementation
130
129
  # e.g. missing polishing of values (e.g. SQL vs MAQL enclosers)
131
130
  gd_maql_filters = []
@@ -2,7 +2,7 @@
2
2
  import argparse
3
3
  import os
4
4
  import re
5
- from typing import Optional, Union
5
+ from typing import Union
6
6
  from urllib.parse import quote_plus
7
7
 
8
8
  import attrs
@@ -97,7 +97,7 @@ class DbtOutputSnowflake(Base):
97
97
  database: str
98
98
  warehouse: str
99
99
  schema: str
100
- query_tag: Optional[str] = None
100
+ query_tag: str | None = None
101
101
 
102
102
  def to_gooddata(self, data_source_id: str, schema_name: str) -> CatalogDataSourceSnowflake:
103
103
  return CatalogDataSourceSnowflake(
@@ -222,7 +222,7 @@ class DbtProfiles:
222
222
  # else do nothing, real value seems to be stored in dbt profile
223
223
 
224
224
  @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:
226
226
  db_type = output_def["type"]
227
227
  if db_type == "postgres":
228
228
  return DbtOutputPostgreSQL.from_dict({"name": output, **output_def})
@@ -3,7 +3,7 @@ import copy
3
3
  import json
4
4
  import re
5
5
  from pathlib import Path
6
- from typing import Optional, Union
6
+ from typing import Union
7
7
 
8
8
  import attrs
9
9
  from gooddata_sdk import CatalogDeclarativeColumn, CatalogDeclarativeTable, CatalogDeclarativeTables
@@ -27,22 +27,23 @@ from gooddata_dbt.dbt.cloud import DbtConnection
27
27
 
28
28
  @attrs.define(auto_attribs=True, kw_only=True)
29
29
  class DbtModelMetaGoodDataTableProps(Base):
30
- model_id: Optional[str] = None
30
+ model_id: str | None = None
31
31
 
32
32
 
33
33
  @attrs.define(auto_attribs=True, kw_only=True)
34
34
  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
43
44
 
44
45
  @property
45
- def gooddata_ref_table_ldm_id(self) -> Optional[str]:
46
+ def gooddata_ref_table_ldm_id(self) -> str | None:
46
47
  if self.referenced_table:
47
48
  return self.referenced_table.lower()
48
49
  return None
@@ -73,7 +74,7 @@ class DbtModelBase(Base):
73
74
  tags: list[str]
74
75
  # If 2+ references point to the same table, the table plays multiple roles,
75
76
  # it must be generated as multiple datasets
76
- role_name: Optional[str] = None
77
+ role_name: str | None = None
77
78
 
78
79
  # TODO - duplicate of backend logic.
79
80
  # Solution: use result of generateLdm as a master template, and override based on dbt metadata only if necessary
@@ -120,7 +121,7 @@ class DbtModelBase(Base):
120
121
 
121
122
  @attrs.define(auto_attribs=True, kw_only=True)
122
123
  class DbtModelColumn(DbtModelBase):
123
- data_type: Optional[str]
124
+ data_type: str | None
124
125
  meta: DbtModelMetaGoodDataColumn = attrs.field(factory=DbtModelMetaGoodDataColumn)
125
126
 
126
127
  # Enable to override LDM ID for LDM entities derived from columns (attributes, ...)
@@ -130,21 +131,21 @@ class DbtModelColumn(DbtModelBase):
130
131
  return self.meta.gooddata.id or self.gooddata_ldm_id
131
132
 
132
133
  @property
133
- def ldm_type(self) -> Optional[str]:
134
+ def ldm_type(self) -> str | None:
134
135
  if self.meta.gooddata.ldm_type is None:
135
136
  return None
136
137
  else:
137
138
  return self.meta.gooddata.ldm_type.value
138
139
 
139
140
  @property
140
- def label_type(self) -> Optional[str]:
141
+ def label_type(self) -> str | None:
141
142
  if self.meta.gooddata.label_type is None:
142
143
  return None
143
144
  else:
144
145
  return self.meta.gooddata.label_type.value
145
146
 
146
147
  @property
147
- def sort_direction(self) -> Optional[str]:
148
+ def sort_direction(self) -> str | None:
148
149
  if self.meta.gooddata.sort_direction is None:
149
150
  return None
150
151
  else:
@@ -387,22 +388,23 @@ class DbtModelTables:
387
388
  return facts
388
389
 
389
390
  @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]:
391
392
  labels = []
392
393
  default_view = None
393
394
  for column in table.columns.values():
394
395
  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)
406
408
  if column.meta.gooddata.default_view:
407
409
  default_view = {
408
410
  "id": column.ldm_id,
@@ -507,7 +509,7 @@ class DbtModelTables:
507
509
  result.append(table)
508
510
  return result
509
511
 
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:
511
513
  result: dict[str, list] = {"datasets": [], "date_instances": []}
512
514
  model_tables = [t for t in self.tables if not model_ids or t.meta.gooddata.model_id in model_ids]
513
515
  role_playing_tables = self.find_role_playing_tables(model_tables)
@@ -517,7 +519,7 @@ class DbtModelTables:
517
519
  result = self.make_dataset(data_source_id, table, role_playing_tables, result)
518
520
  return result
519
521
 
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:
521
523
  comp_table_name = table_name
522
524
  if self.upper_case:
523
525
  comp_table_name = table_name.upper()
@@ -5,7 +5,6 @@ from argparse import Namespace
5
5
  from asyncio import Semaphore
6
6
  from pathlib import Path
7
7
  from time import time
8
- from typing import Optional
9
8
 
10
9
  import tabulate
11
10
  import yaml
@@ -36,7 +35,7 @@ def generate_and_put_ldm(
36
35
  data_source_id: str,
37
36
  workspace_id: str,
38
37
  dbt_tables: DbtModelTables,
39
- model_ids: Optional[list[str]],
38
+ model_ids: list[str] | None,
40
39
  ) -> None:
41
40
  scan_request = CatalogScanModelRequest(scan_tables=True, scan_views=True)
42
41
  logger.info(f"Scan data source {data_source_id=}")
@@ -68,7 +67,7 @@ def deploy_ldm(
68
67
  args: Namespace,
69
68
  all_model_ids: list[str],
70
69
  sdk_wrapper: GoodDataSdkWrapper,
71
- model_ids: Optional[list[str]],
70
+ model_ids: list[str] | None,
72
71
  workspace_id: str,
73
72
  ) -> None:
74
73
  logger.info("Generate and put LDM")
@@ -186,7 +185,7 @@ async def test_visualizations(
186
185
  logger: logging.Logger,
187
186
  sdk_wrapper: GoodDataSdkWrapper,
188
187
  workspace_id: str,
189
- skip_tests: Optional[list[str]],
188
+ skip_tests: list[str] | None,
190
189
  test_visualizations_parallelism: int = 1,
191
190
  ) -> None:
192
191
  start = time()
@@ -334,7 +333,7 @@ def process_organization(
334
333
  logger: logging.Logger,
335
334
  sdk_wrapper: GoodDataSdkWrapper,
336
335
  gd_config: GoodDataConfig,
337
- organization: Optional[GoodDataConfigOrganization] = None,
336
+ organization: GoodDataConfigOrganization | None = None,
338
337
  ) -> None:
339
338
  if args.method == "upload_notification":
340
339
  dbt_profiles = DbtProfiles(args)
@@ -1,5 +1,4 @@
1
1
  # (C) 2023 GoodData Corporation
2
- from typing import Optional
3
2
 
4
3
  import attr
5
4
  import attrs
@@ -37,8 +36,8 @@ class GoodDataConfigProduct(Base):
37
36
  name: str
38
37
  environment_setup_id: str
39
38
  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
42
41
 
43
42
 
44
43
  @attrs.define(auto_attribs=True, kw_only=True)
@@ -49,7 +48,7 @@ class GoodDataConfigOrganization(Base):
49
48
 
50
49
  @attrs.define(auto_attribs=True, kw_only=True)
51
50
  class GoodDataGlobalConfig(Base):
52
- test_visualizations_parallelism: Optional[int] = 1
51
+ test_visualizations_parallelism: int | None = 1
53
52
 
54
53
 
55
54
  @attrs.define(auto_attribs=True, kw_only=True)
@@ -1,7 +1,6 @@
1
1
  # (C) 2023 GoodData Corporation
2
2
  import argparse
3
3
  from logging import Logger
4
- from typing import Optional
5
4
 
6
5
  from gooddata_sdk import GoodDataSdk
7
6
 
@@ -11,7 +10,7 @@ from gooddata_dbt.gooddata.api_wrapper import GoodDataApiWrapper
11
10
  class GoodDataSdkWrapper:
12
11
  # Timeout=600 because supporting waiting for GoodData services to start
13
12
  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
15
14
  ) -> None:
16
15
  self.args = args
17
16
  self.logger = logger
@@ -22,7 +21,7 @@ class GoodDataSdkWrapper:
22
21
  if not self.args.dry_run:
23
22
  self.wait_for_gooddata_is_up(self.timeout)
24
23
 
25
- def get_host_from_sdk(self) -> Optional[str]:
24
+ def get_host_from_sdk(self) -> str | None:
26
25
  # TODO - make _hostname public in gooddata_sdk
27
26
  return self.sdk.client._hostname
28
27
 
@@ -54,7 +53,7 @@ class GoodDataSdkWrapper:
54
53
  self.sdk.support.wait_till_available(timeout=timeout)
55
54
  self.logger.info(f"Host {host} is up")
56
55
 
57
- def pre_cache_visualizations(self, workspaces: Optional[list] = None) -> None:
56
+ def pre_cache_visualizations(self, workspaces: list | None = None) -> None:
58
57
  if not workspaces:
59
58
  workspaces = [w.id for w in self.sdk.catalog_workspace.list_workspaces()]
60
59
  for workspace_id in workspaces:
@@ -1860,6 +1860,25 @@
1860
1860
  "data_type": null,
1861
1861
  "quote": null,
1862
1862
  "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": []
1863
1882
  }
1864
1883
  },
1865
1884
  "meta": {
@@ -21,6 +21,9 @@ columns:
21
21
  - dataType: STRING
22
22
  isPrimaryKey: false
23
23
  name: name
24
+ - dataType: STRING
25
+ isPrimaryKey: false
26
+ name: country
24
27
  - dataType: STRING
25
28
  isPrimaryKey: false
26
29
  name: state
@@ -49,3 +49,35 @@ def test_make_ldm():
49
49
  assert ldm.ldm is not None
50
50
  assert len(ldm.ldm.datasets) == 4
51
51
  assert len(ldm.ldm.date_instances) == 4
52
+
53
+
54
+ FAA_MODEL_ID = "faa"
55
+
56
+
57
+ def test_make_ldm_geo_area():
58
+ """Test that GEO_AREA label type is parsed and included in the LDM."""
59
+ tables = DbtModelTables.from_local(upper_case=False, all_model_ids=[FAA_MODEL_ID], manifest_path=_MANIFEST_PATH)
60
+ scan_pdm = CatalogDeclarativeTables.load_from_disk(_PDM_PATH)
61
+ tables.set_data_types(scan_pdm)
62
+ data_source_id = "postgres"
63
+
64
+ declarative_datasets = tables.make_declarative_datasets(data_source_id, [FAA_MODEL_ID])
65
+ ldm = CatalogDeclarativeModel.from_dict({"ldm": declarative_datasets}, camel_case=False)
66
+
67
+ assert ldm.ldm is not None
68
+ # airports is referenced twice (origin/destination), find either one
69
+ airports = [ds for ds in ldm.ldm.datasets if ds.id == "airports_origin"]
70
+ assert len(airports) == 1
71
+ airports_ds = airports[0]
72
+ # Find the code attribute (which has geo labels)
73
+ code_attrs = [a for a in airports_ds.attributes if a.id == "code_origin"]
74
+ assert len(code_attrs) == 1
75
+ labels = code_attrs[0].labels
76
+ label_types = {label.id: label.value_type for label in labels}
77
+ assert label_types["latitude_origin"] == "GEO_LATITUDE"
78
+ assert label_types["longitude_origin"] == "GEO_LONGITUDE"
79
+ assert label_types["country_origin"] == "GEO_AREA"
80
+ # Verify geo_area_config is propagated
81
+ country_label = next(lbl for lbl in labels if lbl.id == "country_origin")
82
+ assert country_label.geo_area_config is not None
83
+ assert country_label.geo_area_config.collection.id == "countries"
@@ -11,4 +11,4 @@ dependency_groups =
11
11
  setenv =
12
12
  COVERAGE_CORE=sysmon
13
13
  commands =
14
- pytest -v --cov=src/gooddata_dbt --cov-report=xml tests {posargs} --json-report --json-report-file=.json-report-{envname}.json
14
+ pytest -v --cov --cov-report=xml tests {posargs} --json-report --json-report-file=.json-report-{envname}.json