generator.views.table_view

Class to describe a Table View.

  1"""Class to describe a Table View."""
  2
  3from __future__ import annotations
  4
  5from collections import defaultdict
  6from itertools import filterfalse
  7from typing import Any, Dict, Iterator, List, Optional, Set
  8
  9from click import ClickException
 10
 11from . import lookml_utils
 12from .view import OMIT_VIEWS, View, ViewDict
 13
 14
 15class TableView(View):
 16    """A view on any table."""
 17
 18    type: str = "table_view"
 19    measures: Optional[Dict[str, Dict[str, Any]]]
 20
 21    def __init__(
 22        self,
 23        namespace: str,
 24        name: str,
 25        tables: List[Dict[str, str]],
 26        measures: Optional[Dict[str, Dict[str, Any]]] = None,
 27    ):
 28        """Create instance of a TableView."""
 29        super().__init__(namespace, name, TableView.type, tables)
 30        self.measures = measures
 31
 32    @classmethod
 33    def from_db_views(
 34        klass,
 35        namespace: str,
 36        is_glean: bool,
 37        channels: List[Dict[str, str]],
 38        db_views: dict,
 39    ) -> Iterator[TableView]:
 40        """Get Looker views for a namespace."""
 41        view_tables: Dict[str, Dict[str, Dict[str, str]]] = defaultdict(dict)
 42        for channel in channels:
 43            dataset = channel["dataset"]
 44
 45            for view_id, references in db_views[dataset].items():
 46                if view_id in OMIT_VIEWS:
 47                    continue
 48
 49                table_id = f"mozdata.{dataset}.{view_id}"
 50                table: Dict[str, str] = {"table": table_id}
 51                if "channel" in channel:
 52                    table["channel"] = channel["channel"]
 53
 54                view_tables[view_id][table_id] = table
 55
 56        for view_id, tables_by_id in view_tables.items():
 57            yield TableView(namespace, f"{view_id}_table", list(tables_by_id.values()))
 58
 59    @classmethod
 60    def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> TableView:
 61        """Get a view from a name and dict definition."""
 62        return TableView(namespace, name, _dict["tables"], _dict.get("measures"))
 63
 64    def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
 65        """Generate LookML for this view."""
 66        view_defn: Dict[str, Any] = {"name": self.name}
 67
 68        # use schema for the table where channel=="release" or the first one
 69        table = next(
 70            (table for table in self.tables if table.get("channel") == "release"),
 71            self.tables[0],
 72        )["table"]
 73
 74        # add dimensions and dimension groups
 75        dimensions = lookml_utils._generate_dimensions(table, dryrun=dryrun)
 76        view_defn["dimensions"] = list(
 77            filterfalse(lookml_utils._is_dimension_group, dimensions)
 78        )
 79        view_defn["dimension_groups"] = list(
 80            filter(lookml_utils._is_dimension_group, dimensions)
 81        )
 82
 83        # add tag "time_partitioning_field"
 84        time_partitioning_fields: Set[str] = set(
 85            # filter out falsy values
 86            filter(
 87                None, (table.get("time_partitioning_field") for table in self.tables)
 88            )
 89        )
 90        if len(time_partitioning_fields) > 1:
 91            raise ClickException(f"Multiple time_partitioning_fields for {self.name!r}")
 92        elif len(time_partitioning_fields) == 1:
 93            field_name = time_partitioning_fields.pop()
 94            sql = f"${{TABLE}}.{field_name}"
 95            for group_defn in view_defn["dimension_groups"]:
 96                if group_defn["sql"] == sql:
 97                    if "tags" not in group_defn:
 98                        group_defn["tags"] = []
 99                    group_defn["tags"].append("time_partitioning_field")
100                    break
101            else:
102                raise ClickException(
103                    f"time_partitioning_field {field_name!r} not found in {self.name!r}"
104                )
105
106        [project, dataset, table_id] = table.split(".")
107        table_schema = dryrun.create(
108            project=project,
109            dataset=dataset,
110            table=table_id,
111        ).get_table_schema()
112        nested_views = lookml_utils._generate_nested_dimension_views(
113            table_schema, self.name
114        )
115
116        if self.measures:
117            view_defn["measures"] = [
118                {"name": measure_name, **measure_parameters}
119                for measure_name, measure_parameters in self.measures.items()
120            ]
121
122        # parameterize table name
123        if len(self.tables) > 1:
124            view_defn["parameters"] = [
125                {
126                    "name": "channel",
127                    "type": "unquoted",
128                    "default_value": table,
129                    "allowed_values": [
130                        {
131                            "label": _table["channel"].title(),
132                            "value": _table["table"],
133                        }
134                        for _table in self.tables
135                    ],
136                }
137            ]
138            view_defn["sql_table_name"] = "`{% parameter channel %}`"
139        else:
140            view_defn["sql_table_name"] = f"`{table}`"
141
142        return {"views": [view_defn] + nested_views}
class TableView(generator.views.view.View):
 16class TableView(View):
 17    """A view on any table."""
 18
 19    type: str = "table_view"
 20    measures: Optional[Dict[str, Dict[str, Any]]]
 21
 22    def __init__(
 23        self,
 24        namespace: str,
 25        name: str,
 26        tables: List[Dict[str, str]],
 27        measures: Optional[Dict[str, Dict[str, Any]]] = None,
 28    ):
 29        """Create instance of a TableView."""
 30        super().__init__(namespace, name, TableView.type, tables)
 31        self.measures = measures
 32
 33    @classmethod
 34    def from_db_views(
 35        klass,
 36        namespace: str,
 37        is_glean: bool,
 38        channels: List[Dict[str, str]],
 39        db_views: dict,
 40    ) -> Iterator[TableView]:
 41        """Get Looker views for a namespace."""
 42        view_tables: Dict[str, Dict[str, Dict[str, str]]] = defaultdict(dict)
 43        for channel in channels:
 44            dataset = channel["dataset"]
 45
 46            for view_id, references in db_views[dataset].items():
 47                if view_id in OMIT_VIEWS:
 48                    continue
 49
 50                table_id = f"mozdata.{dataset}.{view_id}"
 51                table: Dict[str, str] = {"table": table_id}
 52                if "channel" in channel:
 53                    table["channel"] = channel["channel"]
 54
 55                view_tables[view_id][table_id] = table
 56
 57        for view_id, tables_by_id in view_tables.items():
 58            yield TableView(namespace, f"{view_id}_table", list(tables_by_id.values()))
 59
 60    @classmethod
 61    def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> TableView:
 62        """Get a view from a name and dict definition."""
 63        return TableView(namespace, name, _dict["tables"], _dict.get("measures"))
 64
 65    def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
 66        """Generate LookML for this view."""
 67        view_defn: Dict[str, Any] = {"name": self.name}
 68
 69        # use schema for the table where channel=="release" or the first one
 70        table = next(
 71            (table for table in self.tables if table.get("channel") == "release"),
 72            self.tables[0],
 73        )["table"]
 74
 75        # add dimensions and dimension groups
 76        dimensions = lookml_utils._generate_dimensions(table, dryrun=dryrun)
 77        view_defn["dimensions"] = list(
 78            filterfalse(lookml_utils._is_dimension_group, dimensions)
 79        )
 80        view_defn["dimension_groups"] = list(
 81            filter(lookml_utils._is_dimension_group, dimensions)
 82        )
 83
 84        # add tag "time_partitioning_field"
 85        time_partitioning_fields: Set[str] = set(
 86            # filter out falsy values
 87            filter(
 88                None, (table.get("time_partitioning_field") for table in self.tables)
 89            )
 90        )
 91        if len(time_partitioning_fields) > 1:
 92            raise ClickException(f"Multiple time_partitioning_fields for {self.name!r}")
 93        elif len(time_partitioning_fields) == 1:
 94            field_name = time_partitioning_fields.pop()
 95            sql = f"${{TABLE}}.{field_name}"
 96            for group_defn in view_defn["dimension_groups"]:
 97                if group_defn["sql"] == sql:
 98                    if "tags" not in group_defn:
 99                        group_defn["tags"] = []
100                    group_defn["tags"].append("time_partitioning_field")
101                    break
102            else:
103                raise ClickException(
104                    f"time_partitioning_field {field_name!r} not found in {self.name!r}"
105                )
106
107        [project, dataset, table_id] = table.split(".")
108        table_schema = dryrun.create(
109            project=project,
110            dataset=dataset,
111            table=table_id,
112        ).get_table_schema()
113        nested_views = lookml_utils._generate_nested_dimension_views(
114            table_schema, self.name
115        )
116
117        if self.measures:
118            view_defn["measures"] = [
119                {"name": measure_name, **measure_parameters}
120                for measure_name, measure_parameters in self.measures.items()
121            ]
122
123        # parameterize table name
124        if len(self.tables) > 1:
125            view_defn["parameters"] = [
126                {
127                    "name": "channel",
128                    "type": "unquoted",
129                    "default_value": table,
130                    "allowed_values": [
131                        {
132                            "label": _table["channel"].title(),
133                            "value": _table["table"],
134                        }
135                        for _table in self.tables
136                    ],
137                }
138            ]
139            view_defn["sql_table_name"] = "`{% parameter channel %}`"
140        else:
141            view_defn["sql_table_name"] = f"`{table}`"
142
143        return {"views": [view_defn] + nested_views}

A view on any table.

TableView( namespace: str, name: str, tables: List[Dict[str, str]], measures: Optional[Dict[str, Dict[str, Any]]] = None)
22    def __init__(
23        self,
24        namespace: str,
25        name: str,
26        tables: List[Dict[str, str]],
27        measures: Optional[Dict[str, Dict[str, Any]]] = None,
28    ):
29        """Create instance of a TableView."""
30        super().__init__(namespace, name, TableView.type, tables)
31        self.measures = measures

Create instance of a TableView.

type: str = 'table_view'
measures: Optional[Dict[str, Dict[str, Any]]]
@classmethod
def from_db_views( klass, namespace: str, is_glean: bool, channels: List[Dict[str, str]], db_views: dict) -> Iterator[TableView]:
33    @classmethod
34    def from_db_views(
35        klass,
36        namespace: str,
37        is_glean: bool,
38        channels: List[Dict[str, str]],
39        db_views: dict,
40    ) -> Iterator[TableView]:
41        """Get Looker views for a namespace."""
42        view_tables: Dict[str, Dict[str, Dict[str, str]]] = defaultdict(dict)
43        for channel in channels:
44            dataset = channel["dataset"]
45
46            for view_id, references in db_views[dataset].items():
47                if view_id in OMIT_VIEWS:
48                    continue
49
50                table_id = f"mozdata.{dataset}.{view_id}"
51                table: Dict[str, str] = {"table": table_id}
52                if "channel" in channel:
53                    table["channel"] = channel["channel"]
54
55                view_tables[view_id][table_id] = table
56
57        for view_id, tables_by_id in view_tables.items():
58            yield TableView(namespace, f"{view_id}_table", list(tables_by_id.values()))

Get Looker views for a namespace.

@classmethod
def from_dict( klass, namespace: str, name: str, _dict: generator.views.view.ViewDict) -> TableView:
60    @classmethod
61    def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> TableView:
62        """Get a view from a name and dict definition."""
63        return TableView(namespace, name, _dict["tables"], _dict.get("measures"))

Get a view from a name and dict definition.

def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
 65    def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
 66        """Generate LookML for this view."""
 67        view_defn: Dict[str, Any] = {"name": self.name}
 68
 69        # use schema for the table where channel=="release" or the first one
 70        table = next(
 71            (table for table in self.tables if table.get("channel") == "release"),
 72            self.tables[0],
 73        )["table"]
 74
 75        # add dimensions and dimension groups
 76        dimensions = lookml_utils._generate_dimensions(table, dryrun=dryrun)
 77        view_defn["dimensions"] = list(
 78            filterfalse(lookml_utils._is_dimension_group, dimensions)
 79        )
 80        view_defn["dimension_groups"] = list(
 81            filter(lookml_utils._is_dimension_group, dimensions)
 82        )
 83
 84        # add tag "time_partitioning_field"
 85        time_partitioning_fields: Set[str] = set(
 86            # filter out falsy values
 87            filter(
 88                None, (table.get("time_partitioning_field") for table in self.tables)
 89            )
 90        )
 91        if len(time_partitioning_fields) > 1:
 92            raise ClickException(f"Multiple time_partitioning_fields for {self.name!r}")
 93        elif len(time_partitioning_fields) == 1:
 94            field_name = time_partitioning_fields.pop()
 95            sql = f"${{TABLE}}.{field_name}"
 96            for group_defn in view_defn["dimension_groups"]:
 97                if group_defn["sql"] == sql:
 98                    if "tags" not in group_defn:
 99                        group_defn["tags"] = []
100                    group_defn["tags"].append("time_partitioning_field")
101                    break
102            else:
103                raise ClickException(
104                    f"time_partitioning_field {field_name!r} not found in {self.name!r}"
105                )
106
107        [project, dataset, table_id] = table.split(".")
108        table_schema = dryrun.create(
109            project=project,
110            dataset=dataset,
111            table=table_id,
112        ).get_table_schema()
113        nested_views = lookml_utils._generate_nested_dimension_views(
114            table_schema, self.name
115        )
116
117        if self.measures:
118            view_defn["measures"] = [
119                {"name": measure_name, **measure_parameters}
120                for measure_name, measure_parameters in self.measures.items()
121            ]
122
123        # parameterize table name
124        if len(self.tables) > 1:
125            view_defn["parameters"] = [
126                {
127                    "name": "channel",
128                    "type": "unquoted",
129                    "default_value": table,
130                    "allowed_values": [
131                        {
132                            "label": _table["channel"].title(),
133                            "value": _table["table"],
134                        }
135                        for _table in self.tables
136                    ],
137                }
138            ]
139            view_defn["sql_table_name"] = "`{% parameter channel %}`"
140        else:
141            view_defn["sql_table_name"] = f"`{table}`"
142
143        return {"views": [view_defn] + nested_views}

Generate LookML for this view.