generator.views.view
Generic class to describe Looker views.
1"""Generic class to describe Looker views.""" 2 3from __future__ import annotations 4 5from typing import Any, Dict, Iterator, List, Optional, Set, TypedDict 6 7from click import ClickException 8 9OMIT_VIEWS: Set[str] = set() 10 11 12# TODO: Once we upgrade to Python 3.11 mark just `measures` as non-required, not all keys. 13class ViewDict(TypedDict, total=False): 14 """Represent a view definition.""" 15 16 type: str 17 tables: List[Dict[str, str]] 18 measures: Dict[str, Dict[str, Any]] 19 20 21class View(object): 22 """A generic Looker View.""" 23 24 name: str 25 view_type: str 26 tables: List[Dict[str, Any]] 27 namespace: str 28 29 def __init__( 30 self, 31 namespace: str, 32 name: str, 33 view_type: str, 34 tables: List[Dict[str, Any]], 35 **kwargs, 36 ): 37 """Create an instance of a view.""" 38 self.namespace = namespace 39 self.tables = tables 40 self.name = name 41 self.view_type = view_type 42 43 @classmethod 44 def from_db_views( 45 klass, 46 namespace: str, 47 is_glean: bool, 48 channels: List[Dict[str, str]], 49 db_views: dict, 50 ) -> Iterator[View]: 51 """Get Looker views from app.""" 52 raise NotImplementedError("Only implemented in subclass.") 53 54 @classmethod 55 def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> View: 56 """Get a view from a name and dict definition.""" 57 raise NotImplementedError("Only implemented in subclass.") 58 59 def get_type(self) -> str: 60 """Get the type of this view.""" 61 return self.view_type 62 63 def as_dict(self) -> dict: 64 """Get this view as a dictionary.""" 65 return { 66 "type": self.view_type, 67 "tables": self.tables, 68 } 69 70 def __str__(self): 71 """Stringify.""" 72 return f"name: {self.name}, type: {self.type}, table: {self.tables}, namespace: {self.namespace}" 73 74 def __eq__(self, other) -> bool: 75 """Check for equality with other View.""" 76 77 def comparable_dict(d): 78 return {tuple(sorted([(k, str(v)) for k, v in t.items()])) for t in d} 79 80 if isinstance(other, View): 81 return ( 82 self.name == other.name 83 and self.view_type == other.view_type 84 and comparable_dict(self.tables) == comparable_dict(other.tables) 85 and self.namespace == other.namespace 86 ) 87 return False 88 89 def get_dimensions( 90 self, table, v1_name: Optional[str], dryrun 91 ) -> List[Dict[str, Any]]: 92 """Get the set of dimensions for this view.""" 93 raise NotImplementedError("Only implemented in subclass.") 94 95 def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]: 96 """ 97 Generate Lookml for this view. 98 99 View instances can generate more than one Looker view, 100 for e.g. nested fields and joins, so this returns 101 a list. 102 """ 103 raise NotImplementedError("Only implemented in subclass.") 104 105 def get_client_id(self, dimensions: List[dict], table: str) -> Optional[str]: 106 """Return the first field that looks like a client identifier.""" 107 client_id_fields = self.select_dimension( 108 {"client_id", "client_info__client_id", "context_id"}, 109 dimensions, 110 table, 111 ) 112 # Some pings purposely disinclude client_ids, e.g. firefox installer 113 return client_id_fields["name"] if client_id_fields else None 114 115 def get_document_id(self, dimensions: List[dict], table: str) -> Optional[str]: 116 """Return the first field that looks like a document_id.""" 117 document_id = self.select_dimension("document_id", dimensions, table) 118 return document_id["name"] if document_id else None 119 120 def select_dimension( 121 self, 122 dimension_names: str | set[str], 123 dimensions: List[dict], 124 table: str, 125 ) -> Optional[dict[str, str]]: 126 """ 127 Return the first field that matches dimension name. 128 129 Throws if the query set is greater than one and more than one item is selected. 130 """ 131 if isinstance(dimension_names, str): 132 dimension_names = {dimension_names} 133 selected = [d for d in dimensions if d["name"] in dimension_names] 134 if selected: 135 # there should only be one dimension selected from the set 136 # if there are multiple options in the dimention_names set. 137 if len(dimension_names) > 1 and len(selected) > 1: 138 raise ClickException( 139 f"Duplicate {'/'.join(dimension_names)} dimension in {table!r}" 140 ) 141 return selected[0] 142 return None
OMIT_VIEWS: Set[str] =
set()
class
ViewDict(typing.TypedDict):
14class ViewDict(TypedDict, total=False): 15 """Represent a view definition.""" 16 17 type: str 18 tables: List[Dict[str, str]] 19 measures: Dict[str, Dict[str, Any]]
Represent a view definition.
class
View:
22class View(object): 23 """A generic Looker View.""" 24 25 name: str 26 view_type: str 27 tables: List[Dict[str, Any]] 28 namespace: str 29 30 def __init__( 31 self, 32 namespace: str, 33 name: str, 34 view_type: str, 35 tables: List[Dict[str, Any]], 36 **kwargs, 37 ): 38 """Create an instance of a view.""" 39 self.namespace = namespace 40 self.tables = tables 41 self.name = name 42 self.view_type = view_type 43 44 @classmethod 45 def from_db_views( 46 klass, 47 namespace: str, 48 is_glean: bool, 49 channels: List[Dict[str, str]], 50 db_views: dict, 51 ) -> Iterator[View]: 52 """Get Looker views from app.""" 53 raise NotImplementedError("Only implemented in subclass.") 54 55 @classmethod 56 def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> View: 57 """Get a view from a name and dict definition.""" 58 raise NotImplementedError("Only implemented in subclass.") 59 60 def get_type(self) -> str: 61 """Get the type of this view.""" 62 return self.view_type 63 64 def as_dict(self) -> dict: 65 """Get this view as a dictionary.""" 66 return { 67 "type": self.view_type, 68 "tables": self.tables, 69 } 70 71 def __str__(self): 72 """Stringify.""" 73 return f"name: {self.name}, type: {self.type}, table: {self.tables}, namespace: {self.namespace}" 74 75 def __eq__(self, other) -> bool: 76 """Check for equality with other View.""" 77 78 def comparable_dict(d): 79 return {tuple(sorted([(k, str(v)) for k, v in t.items()])) for t in d} 80 81 if isinstance(other, View): 82 return ( 83 self.name == other.name 84 and self.view_type == other.view_type 85 and comparable_dict(self.tables) == comparable_dict(other.tables) 86 and self.namespace == other.namespace 87 ) 88 return False 89 90 def get_dimensions( 91 self, table, v1_name: Optional[str], dryrun 92 ) -> List[Dict[str, Any]]: 93 """Get the set of dimensions for this view.""" 94 raise NotImplementedError("Only implemented in subclass.") 95 96 def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]: 97 """ 98 Generate Lookml for this view. 99 100 View instances can generate more than one Looker view, 101 for e.g. nested fields and joins, so this returns 102 a list. 103 """ 104 raise NotImplementedError("Only implemented in subclass.") 105 106 def get_client_id(self, dimensions: List[dict], table: str) -> Optional[str]: 107 """Return the first field that looks like a client identifier.""" 108 client_id_fields = self.select_dimension( 109 {"client_id", "client_info__client_id", "context_id"}, 110 dimensions, 111 table, 112 ) 113 # Some pings purposely disinclude client_ids, e.g. firefox installer 114 return client_id_fields["name"] if client_id_fields else None 115 116 def get_document_id(self, dimensions: List[dict], table: str) -> Optional[str]: 117 """Return the first field that looks like a document_id.""" 118 document_id = self.select_dimension("document_id", dimensions, table) 119 return document_id["name"] if document_id else None 120 121 def select_dimension( 122 self, 123 dimension_names: str | set[str], 124 dimensions: List[dict], 125 table: str, 126 ) -> Optional[dict[str, str]]: 127 """ 128 Return the first field that matches dimension name. 129 130 Throws if the query set is greater than one and more than one item is selected. 131 """ 132 if isinstance(dimension_names, str): 133 dimension_names = {dimension_names} 134 selected = [d for d in dimensions if d["name"] in dimension_names] 135 if selected: 136 # there should only be one dimension selected from the set 137 # if there are multiple options in the dimention_names set. 138 if len(dimension_names) > 1 and len(selected) > 1: 139 raise ClickException( 140 f"Duplicate {'/'.join(dimension_names)} dimension in {table!r}" 141 ) 142 return selected[0] 143 return None
A generic Looker View.
View( namespace: str, name: str, view_type: str, tables: List[Dict[str, Any]], **kwargs)
30 def __init__( 31 self, 32 namespace: str, 33 name: str, 34 view_type: str, 35 tables: List[Dict[str, Any]], 36 **kwargs, 37 ): 38 """Create an instance of a view.""" 39 self.namespace = namespace 40 self.tables = tables 41 self.name = name 42 self.view_type = view_type
Create an instance of a view.
@classmethod
def
from_db_views( klass, namespace: str, is_glean: bool, channels: List[Dict[str, str]], db_views: dict) -> Iterator[View]:
44 @classmethod 45 def from_db_views( 46 klass, 47 namespace: str, 48 is_glean: bool, 49 channels: List[Dict[str, str]], 50 db_views: dict, 51 ) -> Iterator[View]: 52 """Get Looker views from app.""" 53 raise NotImplementedError("Only implemented in subclass.")
Get Looker views from app.
55 @classmethod 56 def from_dict(klass, namespace: str, name: str, _dict: ViewDict) -> View: 57 """Get a view from a name and dict definition.""" 58 raise NotImplementedError("Only implemented in subclass.")
Get a view from a name and dict definition.
def
as_dict(self) -> dict:
64 def as_dict(self) -> dict: 65 """Get this view as a dictionary.""" 66 return { 67 "type": self.view_type, 68 "tables": self.tables, 69 }
Get this view as a dictionary.
def
get_dimensions(self, table, v1_name: Optional[str], dryrun) -> List[Dict[str, Any]]:
90 def get_dimensions( 91 self, table, v1_name: Optional[str], dryrun 92 ) -> List[Dict[str, Any]]: 93 """Get the set of dimensions for this view.""" 94 raise NotImplementedError("Only implemented in subclass.")
Get the set of dimensions for this view.
def
to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]:
96 def to_lookml(self, v1_name: Optional[str], dryrun) -> Dict[str, Any]: 97 """ 98 Generate Lookml for this view. 99 100 View instances can generate more than one Looker view, 101 for e.g. nested fields and joins, so this returns 102 a list. 103 """ 104 raise NotImplementedError("Only implemented in subclass.")
Generate Lookml for this view.
View instances can generate more than one Looker view, for e.g. nested fields and joins, so this returns a list.
def
get_client_id(self, dimensions: List[dict], table: str) -> Optional[str]:
106 def get_client_id(self, dimensions: List[dict], table: str) -> Optional[str]: 107 """Return the first field that looks like a client identifier.""" 108 client_id_fields = self.select_dimension( 109 {"client_id", "client_info__client_id", "context_id"}, 110 dimensions, 111 table, 112 ) 113 # Some pings purposely disinclude client_ids, e.g. firefox installer 114 return client_id_fields["name"] if client_id_fields else None
Return the first field that looks like a client identifier.
def
get_document_id(self, dimensions: List[dict], table: str) -> Optional[str]:
116 def get_document_id(self, dimensions: List[dict], table: str) -> Optional[str]: 117 """Return the first field that looks like a document_id.""" 118 document_id = self.select_dimension("document_id", dimensions, table) 119 return document_id["name"] if document_id else None
Return the first field that looks like a document_id.
def
select_dimension( self, dimension_names: str | set[str], dimensions: List[dict], table: str) -> Optional[dict[str, str]]:
121 def select_dimension( 122 self, 123 dimension_names: str | set[str], 124 dimensions: List[dict], 125 table: str, 126 ) -> Optional[dict[str, str]]: 127 """ 128 Return the first field that matches dimension name. 129 130 Throws if the query set is greater than one and more than one item is selected. 131 """ 132 if isinstance(dimension_names, str): 133 dimension_names = {dimension_names} 134 selected = [d for d in dimensions if d["name"] in dimension_names] 135 if selected: 136 # there should only be one dimension selected from the set 137 # if there are multiple options in the dimention_names set. 138 if len(dimension_names) > 1 and len(selected) > 1: 139 raise ClickException( 140 f"Duplicate {'/'.join(dimension_names)} dimension in {table!r}" 141 ) 142 return selected[0] 143 return None
Return the first field that matches dimension name.
Throws if the query set is greater than one and more than one item is selected.