generator/views/table_view.py (109 lines of code) (raw):

"""Class to describe a Table View.""" from __future__ import annotations from collections import defaultdict from itertools import filterfalse from typing import Any, Dict, Iterator, List, Optional, Set from click import ClickException from . import lookml_utils from .view import OMIT_VIEWS, View, ViewDict class TableView(View): """A view on any table.""" type: str = "table_view" measures: Optional[Dict[str, Dict[str, Any]]] def __init__( self, namespace: str, name: str, tables: List[Dict[str, str]], measures: Optional[Dict[str, Dict[str, Any]]] = None, ): """Create instance of a TableView.""" super().__init__(namespace, name, TableView.type, tables) self.measures = measures @classmethod def from_db_views( klass, namespace: str, is_glean: bool, channels: List[Dict[str, str]], db_views: dict, ) -> Iterator[TableView]: """Get Looker views for a namespace.""" view_tables: Dict[str, Dict[str, Dict[str, str]]] = defaultdict(dict) for channel in channels: dataset = channel["dataset"] for view_id, references in db_views[dataset].items(): if view_id in OMIT_VIEWS: continue table_id = f"mozdata.{dataset}.{view_id}" table: Dict[str, str] = {"table": table_id} if "channel" in channel: table["channel"] = channel["channel"] view_tables[view_id][table_id] = table for view_id, tables_by_id in view_tables.items(): yield TableView(namespace, f"{view_id}_table", list(tables_by_id.values())) @classmethod def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> TableView: """Get a view from a name and dict definition.""" return TableView(namespace, name, _dict["tables"], _dict.get("measures")) def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]: """Generate LookML for this view.""" view_defn: Dict[str, Any] = {"name": self.name} # use schema for the table where channel=="release" or the first one table = next( (table for table in self.tables if table.get("channel") == "release"), self.tables[0], )["table"] # add dimensions and dimension groups dimensions = lookml_utils._generate_dimensions(table, dryrun=dryrun) view_defn["dimensions"] = list( filterfalse(lookml_utils._is_dimension_group, dimensions) ) view_defn["dimension_groups"] = list( filter(lookml_utils._is_dimension_group, dimensions) ) # add tag "time_partitioning_field" time_partitioning_fields: Set[str] = set( # filter out falsy values filter( None, (table.get("time_partitioning_field") for table in self.tables) ) ) if len(time_partitioning_fields) > 1: raise ClickException(f"Multiple time_partitioning_fields for {self.name!r}") elif len(time_partitioning_fields) == 1: field_name = time_partitioning_fields.pop() sql = f"${{TABLE}}.{field_name}" for group_defn in view_defn["dimension_groups"]: if group_defn["sql"] == sql: if "tags" not in group_defn: group_defn["tags"] = [] group_defn["tags"].append("time_partitioning_field") break else: raise ClickException( f"time_partitioning_field {field_name!r} not found in {self.name!r}" ) [project, dataset, table_id] = table.split(".") table_schema = dryrun.create( project=project, dataset=dataset, table=table_id, ).get_table_schema() nested_views = lookml_utils._generate_nested_dimension_views( table_schema, self.name ) if self.measures: view_defn["measures"] = [ {"name": measure_name, **measure_parameters} for measure_name, measure_parameters in self.measures.items() ] # parameterize table name if len(self.tables) > 1: view_defn["parameters"] = [ { "name": "channel", "type": "unquoted", "default_value": table, "allowed_values": [ { "label": _table["channel"].title(), "value": _table["table"], } for _table in self.tables ], } ] view_defn["sql_table_name"] = "`{% parameter channel %}`" else: view_defn["sql_table_name"] = f"`{table}`" return {"views": [view_defn] + nested_views}