Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 8a0d001

Browse files
committed
feat: folder creation and extra measures generation (Issue #22, #18) and fix tests
1 parent 5118722 commit 8a0d001

7 files changed

Lines changed: 97 additions & 9 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,4 @@ result.json
171171
.vscode/
172172

173173
.DS_Store
174+
.envrc

dataform2looker/database_mappers.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,18 @@ class GenericTable:
9090
UnsupportedDatabaseTypeError: If an unsupported `db_type` is provided.
9191
""" # noqa: E501
9292

93-
def __init__(self, table_id: str, db_type: str = "bigquery") -> None:
93+
def __init__(
94+
self,
95+
table_id: str,
96+
db_type: str = "bigquery",
97+
gen_extra_measures: bool = False,
98+
) -> None:
9499
"""Initializes the `GenericTable` object based on the database type.
95100
96101
Args:
97102
table_id: The full ID of the table in the database.
98103
db_type: The type of the database ("bigquery" currently supported).
104+
gen_extra_measures: Whether to generate extra measures (e.g., sums, count_distinct).
99105
100106
Raises:
101107
UnsupportedDatabaseTypeError: If an unsupported `db_type` is provided.
@@ -127,9 +133,23 @@ def __init__(self, table_id: str, db_type: str = "bigquery") -> None:
127133
f"Dimensions Group for table {self.table_name}: {self.dimension_group}"
128134
)
129135
self.measures = [{"type": "count", "name": "count"}]
130-
# TODO it should be possible to include other measures by passing an argument
131-
# Include measures if needed such as sums of all number dimensions
132-
# include count_distinct
136+
if gen_extra_measures:
137+
for column in self.__table.columns:
138+
if (
139+
column.dimension_type == "dimension"
140+
and column.field_type == "number"
141+
):
142+
self.measures.append({
143+
"type": "sum",
144+
"name": f"total_{column.name}",
145+
"sql": f"${{TABLE}}.{column.name}",
146+
})
147+
if column.dimension_type == "dimension":
148+
self.measures.append({
149+
"type": "count_distinct",
150+
"name": f"count_distinct_{column.name}",
151+
"sql": f"${{TABLE}}.{column.name}",
152+
})
133153

134154
self.table_dictionary = {
135155
"view": {

dataform2looker/dataform2looker.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,28 @@
1010
from dataform2looker.lookml import LookML
1111

1212

13-
def _generate_view(path_to_json_file: str, target_dir: str, tags: set[str]) -> int:
13+
def _generate_view(
14+
path_to_json_file: str, target_dir: str, tags: set[str], gen_extra_measures: bool
15+
) -> int:
1416
"""Generates LookML view files from a Dataform model.
1517
1618
Args:
1719
path_to_json_file (str): Path to the JSON file from compiled Dataform project.
1820
target_dir (str): Target directory for Looker views.
1921
tags (set[str]): Filter to dataform models using this tag.
22+
gen_extra_measures (bool): Whether to generate extra measures.
2023
2124
Returns:
2225
int: 0 if the view generation was successful, 1 otherwise.
2326
"""
2427
logging.info(f" Generating views from: {path_to_json_file}")
2528
try:
26-
lookml_object = LookML(path_to_json_file, target_dir, tags=tags)
29+
lookml_object = LookML(
30+
path_to_json_file,
31+
target_dir,
32+
tags=tags,
33+
gen_extra_measures=gen_extra_measures,
34+
)
2735
lookml_object.save_lookml_views()
2836
return 0
2937
except subprocess.CalledProcessError as e:
@@ -70,17 +78,26 @@ def main(argv: Sequence[str] | None = None) -> int:
7078
required=False,
7179
)
7280

81+
parser.add_argument(
82+
"--gen-extra-measures",
83+
action="store_true",
84+
help="Generate extra measures like sum and count distinct.",
85+
)
86+
7387
args = parser.parse_args(argv)
7488

7589
source_file = args.source_file_path
7690
target_dir = args.target_dir
7791
verbose = args.verbose
7892
tags = args.tags
93+
gen_extra_measures = args.gen_extra_measures
7994

8095
logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
8196

8297
if source_file.is_file():
8398
logging.info(f" Processing file: {source_file}")
84-
return _generate_view(str(source_file), str(target_dir), set(tags))
99+
return _generate_view(
100+
str(source_file), str(target_dir), set(tags), gen_extra_measures
101+
)
85102
logging.error("The provided path is not taking to a JSON file")
86103
sys.exit(1)

dataform2looker/lookml.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import logging
5+
import os
56

67
import lkml
78

@@ -25,6 +26,7 @@ def __init__(
2526
target_folder_path: str,
2627
db_type: str = "bigquery",
2728
tags: list[str] = None,
29+
gen_extra_measures: bool = False,
2830
) -> None:
2931
"""Initializes the `LookML` object.
3032
@@ -33,17 +35,20 @@ def __init__(
3335
target_folder_path: The target folder for LookML view files.
3436
db_type: The type of the database ("bigquery" currently supported).
3537
tags: A list of tags to filter tables (not yet implemented).
38+
gen_extra_measures: Whether to generate extra measures.
3639
""" # noqa: E501
3740
self.source_json_path = source_json_path
3841
self.db_type = db_type
3942
self.tags = set(tags or [])
43+
self.gen_extra_measures = gen_extra_measures
4044
self.__tables_ids = self.__get_list_of_table_ids()
4145
self.__tables_list = self.__initialize_tables(self.__tables_ids)
4246
self.lookml_templates = self.__generate_lookml_templates(self.__tables_list)
4347
self.target_folder_path = target_folder_path
4448

4549
def save_lookml_views(self) -> None:
4650
"""Generates and saves LookML view files for each table.""" # noqa: E501
51+
os.makedirs(self.target_folder_path, exist_ok=True)
4752
for table_name, table_template in self.lookml_templates.items():
4853
file_path = f"{self.target_folder_path}/" f"{table_name}.view.lkml"
4954
logging.debug(f"Creating file {file_path}")
@@ -84,7 +89,10 @@ def __initialize_tables(
8489
Returns:
8590
A list of `GenericTable` objects representing the tables.
8691
""" # noqa: E501
87-
tables_list = [GenericTable(table_id, self.db_type) for table_id in tables_ids]
92+
tables_list = [
93+
GenericTable(table_id, self.db_type, self.gen_extra_measures)
94+
for table_id in tables_ids
95+
]
8896
return tables_list
8997

9098
def __get_list_of_table_ids(self) -> list[str]:

tests/test_column.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_dimension_dictionar(self, my_column: Column) -> None:
4545
"name": my_column_name,
4646
"type": my_field_type,
4747
"description": my_column_description,
48-
"sql": f"{{TABLE}}.{my_column_name}",
48+
"sql": f"${{TABLE}}.{my_column_name}",
4949
}
5050
assert my_column.column_dictionary == column_dictionary
5151

tests/test_generictable.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,17 @@ def test_table_dictionary_measures(self, my_generic_table: GenericTable) -> None
6363
assert "view" in table_dictionary
6464
assert "measures" in table_dictionary["view"]
6565

66+
def test_gen_extra_measures(self, bq_table_id: str) -> None:
67+
"""Tests that extra measures are generated when `gen_extra_measures` is True."""
68+
table = GenericTable(bq_table_id, gen_extra_measures=True)
69+
measures = table.table_dictionary["view"]["measures"]
70+
71+
# Should have count + sum for number columns + count_distinct for all dimensions
72+
# In the mock data, let's see what we have.
73+
# From conftest.py or dataform_result.json
74+
assert any(m["type"] == "sum" for m in measures)
75+
assert any(m["type"] == "count_distinct" for m in measures)
76+
assert any(m["name"] == "count" for m in measures)
77+
6678

6779
# TODO add tests to check the table_dictionary

tests/test_lookml.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""This module contains unit tests for the `LookML` class from the `dataform2looker.lookml` module.""" # noqa: E501
22

3+
import os
4+
import shutil
5+
36
import pytest
47

58
from dataform2looker.lookml import LookML
@@ -58,5 +61,32 @@ def test_measure_generation(self, my_lookml: LookML) -> None:
5861
in view.strip()
5962
)
6063

64+
def test_extra_measure_generation(
65+
self, source_json_path: str, target_folder_path: str
66+
) -> None:
67+
"""Tests that extra measures are generated in LookML templates."""
68+
my_lookml = LookML(
69+
source_json_path, target_folder_path, gen_extra_measures=True
70+
)
71+
for view in my_lookml.lookml_templates.values():
72+
assert "type: sum" in view
73+
assert "type: count_distinct" in view
74+
75+
def test_save_lookml_views_creates_folder(
76+
self, source_json_path: str, tmp_path: str
77+
) -> None:
78+
"""Tests folder creation in `save_lookml_views`."""
79+
target_folder = os.path.join(tmp_path, "new_views_folder")
80+
if os.path.exists(target_folder):
81+
shutil.rmtree(target_folder)
82+
83+
lookml = LookML(source_json_path, target_folder)
84+
lookml.save_lookml_views()
85+
86+
assert os.path.exists(target_folder)
87+
assert os.path.isdir(target_folder)
88+
# Check if files were created
89+
assert len(os.listdir(target_folder)) == 2
90+
6191

6292
# TODO include a test for the generated template

0 commit comments

Comments
 (0)