1
+
2
+ import re
3
+ import traceback
4
+ import typer
5
+
6
+ from pprint import pformat
7
+
8
+ from lkml2cube .parser .views import parse_view
9
+
10
+ snake_case = r'\{([a-zA-Z]+(?:_[a-zA-Z]+)*\.[a-zA-Z]+(?:_[a-zA-Z]+)*)\}'
11
+
12
+ def snakify (s ):
13
+ return '_' .join (
14
+ re .sub ('([A-Z][a-z]+)' , r' \1' ,
15
+ re .sub ('([A-Z]+)' , r' \1' ,
16
+ s .replace ('-' , ' ' ))).split ()
17
+ ).lower ()
18
+
19
+ def build_cube_name_look_up (cube_def ):
20
+ if 'cube_name_look_up' in cube_def :
21
+ return
22
+ cube_name_look_up = {}
23
+ for cube_element in cube_def ['cubes' ]:
24
+ cube_name_look_up [cube_element ['name' ]] = cube_element
25
+ cube_def ['cube_name_look_up' ] = cube_name_look_up
26
+
27
+ def get_cube_from_cube_def (cube_def , cube_name ):
28
+ if 'cube_name_look_up' not in cube_def :
29
+ build_cube_name_look_up (cube_def )
30
+ if cube_name in cube_def ['cube_name_look_up' ]:
31
+ return cube_def ['cube_name_look_up' ][cube_name ]
32
+ return None
33
+
34
+ def get_cube_names_from_join_condition (join_condition ):
35
+ return [cube .split ('.' )[0 ] for cube in re .findall (snake_case , join_condition )]
36
+
37
+ def traverse_graph (join_paths , cube_left , cube_right ):
38
+ # Create a queue for BFS
39
+ queue = []
40
+ queue .append ([cube_left ])
41
+
42
+ while queue :
43
+ #Dequeue a vertex from queue
44
+ tmp_path = queue .pop (0 )
45
+ # If this adjacent node is the destination node,
46
+ # then return true
47
+ last_node = tmp_path [len (tmp_path )- 1 ]
48
+ if last_node == cube_right :
49
+ return '.' .join (tmp_path )
50
+ # Else, continue to do BFS
51
+ if last_node in join_paths :
52
+ for cube in join_paths [last_node ]:
53
+ if cube not in tmp_path :
54
+ new_path = []
55
+ new_path = tmp_path + [cube ]
56
+ queue .append (new_path )
57
+
58
+ typer .echo (f'Cubes are not reachable: { cube_left } , { cube_right } ' )
59
+ return '.' .join (cube_left , cube_right )
60
+
61
+
62
+ def generate_cube_joins (cube_def , lookml_model ):
63
+ for explore in lookml_model ['explores' ]:
64
+ if 'joins' not in explore :
65
+ continue
66
+
67
+ for join_element in explore ['joins' ]:
68
+ try :
69
+ cube_right = join_element ['name' ]
70
+
71
+ joined_cubes = [cube for cube in get_cube_names_from_join_condition (join_element ['sql_on' ]) if cube != cube_right ]
72
+ if joined_cubes :
73
+ if 'from' in join_element :
74
+ cube = {
75
+ 'name' : cube_right ,
76
+ 'extends' : join_element ['from' ],
77
+ 'shown' : False ,
78
+ }
79
+ cube_def ['cubes' ].append (cube )
80
+ else :
81
+ cube = get_cube_from_cube_def (cube_def , cube_right )
82
+
83
+ join_condition = join_element ['sql_on' ]
84
+
85
+ if 'joins' not in cube :
86
+ cube ['joins' ] = []
87
+
88
+ cube ['joins' ].append ({
89
+ 'name' : joined_cubes [0 ],
90
+ 'sql' : join_condition ,
91
+ 'relationship' : join_element ['relationship' ]
92
+ })
93
+ except Exception :
94
+ typer .echo (f'Error while parsing explore: { pformat (explore )} ' )
95
+ typer .echo (traceback .format_exc ())
96
+
97
+ return cube_def
98
+
99
+ def generate_cube_views (cube_def , lookml_model ):
100
+ if 'views' not in cube_def :
101
+ cube_def ['views' ] = []
102
+ for explore in lookml_model ['explores' ]:
103
+ try :
104
+ central_cube = explore ['name' ]
105
+ view_name = snakify (explore ['label' ])
106
+ view = {
107
+ 'name' : view_name ,
108
+ 'description' : explore ['label' ],
109
+ 'cubes' : [{
110
+ 'join_path' : central_cube ,
111
+ 'includes' : "*" ,
112
+ 'alias' : view_name
113
+ }]
114
+ }
115
+
116
+ if 'joins' not in explore :
117
+ cube_def ['views' ].append (view )
118
+ continue
119
+ # Create Graph
120
+ join_paths = {}
121
+ for join_element in explore ['joins' ]:
122
+ cube_right = join_element ['name' ]
123
+ cube_left = [cube for cube in get_cube_names_from_join_condition (join_element ['sql_on' ]) if cube != cube_right ][0 ]
124
+
125
+ if cube_left in join_paths :
126
+ join_paths [cube_left ].append (cube_right )
127
+ else :
128
+ join_paths [cube_left ] = [cube_right ]
129
+ # traverse graph
130
+ for join_element in explore ['joins' ]:
131
+ cube_right = join_element ['name' ]
132
+ join_path = {
133
+ 'join_path' : traverse_graph (join_paths , central_cube , cube_right ),
134
+ 'includes' : "*" ,
135
+ 'alias' : cube_right
136
+ }
137
+ view ['cubes' ].append (join_path )
138
+
139
+ # End
140
+ cube_def ['views' ].append (view )
141
+
142
+ except Exception :
143
+ typer .echo (f'Error while parsing explore: { pformat (explore )} ' )
144
+ typer .echo (traceback .format_exc ())
145
+ return cube_def
146
+
147
+
148
+ def parse_explores (lookml_model ):
149
+ # First we read all possible lookml views.
150
+ cube_def = parse_view (lookml_model , raise_when_views_not_present = False )
151
+ if 'explores' not in lookml_model :
152
+ raise Exception ('LookML explores are needed to generate Cube Views, no explore found in path.' )
153
+ cube_def = generate_cube_joins (cube_def , lookml_model )
154
+
155
+ cube_def = generate_cube_views (cube_def , lookml_model )
156
+
157
+ return cube_def
0 commit comments