oracle-data-studio 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adp/__init__.py +58 -0
- adp/adp.py +70 -0
- adp/adp_analytics.py +484 -0
- adp/adp_ingest.py +805 -0
- adp/adp_insight.py +206 -0
- adp/adp_misc.py +459 -0
- adp/db_util.py +117 -0
- adp/rest.py +244 -0
- oracle_data_studio-1.0.0.dist-info/LICENSE.txt +17 -0
- oracle_data_studio-1.0.0.dist-info/METADATA +55 -0
- oracle_data_studio-1.0.0.dist-info/RECORD +14 -0
- oracle_data_studio-1.0.0.dist-info/THIRD_PARTY_LICENSES.txt +448 -0
- oracle_data_studio-1.0.0.dist-info/WHEEL +5 -0
- oracle_data_studio-1.0.0.dist-info/top_level.txt +1 -0
adp/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023-2024, Oracle and/or its affiliates.
|
|
5
|
+
|
|
6
|
+
ORDS API Python Client
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from .rest import Rest
|
|
11
|
+
from .db_util import DbUtils
|
|
12
|
+
from .adp_analytics import AdpAnalytics
|
|
13
|
+
from .adp_ingest import AdpIngest
|
|
14
|
+
from .adp_insight import AdpInsight
|
|
15
|
+
from .adp_misc import AdpMisc
|
|
16
|
+
|
|
17
|
+
from .adp import Adp
|
|
18
|
+
|
|
19
|
+
def login(url : str, username : str, password : str) -> Adp:
|
|
20
|
+
''' Login to the ORDS
|
|
21
|
+
|
|
22
|
+
@param url (String) - url for ORDS with protocol, host and port
|
|
23
|
+
Another parameters:
|
|
24
|
+
@param username (String) - name of the schema
|
|
25
|
+
@param password (String) - password of the schema
|
|
26
|
+
'''
|
|
27
|
+
|
|
28
|
+
rest = Rest()
|
|
29
|
+
rest.login(url, username, password)
|
|
30
|
+
ords = Adp(rest)
|
|
31
|
+
return ords
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def connect(url: str = None) -> Adp:
|
|
35
|
+
''' Login to the ORDS using database cursor. This function does not require url, username, and password.
|
|
36
|
+
Url and username are taken from sql queries, client Id and client secret are generated when they does not exist.
|
|
37
|
+
Login is performing using OAuth access token
|
|
38
|
+
'''
|
|
39
|
+
|
|
40
|
+
#pylint: disable=C0415
|
|
41
|
+
try:
|
|
42
|
+
import oml
|
|
43
|
+
cursor=oml.cursor()
|
|
44
|
+
db_utils = DbUtils()
|
|
45
|
+
db_utils.set_cursor(cursor)
|
|
46
|
+
if url is None:
|
|
47
|
+
url = db_utils.get_url()
|
|
48
|
+
username = db_utils.get_username()
|
|
49
|
+
|
|
50
|
+
client = db_utils.get_client()
|
|
51
|
+
|
|
52
|
+
rest = Rest()
|
|
53
|
+
rest.connect(url, username, client)
|
|
54
|
+
ords = Adp(rest)
|
|
55
|
+
return ords
|
|
56
|
+
|
|
57
|
+
except ImportError as exp:
|
|
58
|
+
raise ImportError("OML is not defined") from exp
|
adp/adp.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023-2024, Oracle and/or its affiliates.
|
|
5
|
+
|
|
6
|
+
'''
|
|
7
|
+
|
|
8
|
+
from .adp_misc import AdpMisc
|
|
9
|
+
from .adp_analytics import AdpAnalytics
|
|
10
|
+
from .adp_ingest import AdpIngest
|
|
11
|
+
from .adp_insight import AdpInsight
|
|
12
|
+
from .rest import Rest
|
|
13
|
+
|
|
14
|
+
class Adp:
|
|
15
|
+
'''
|
|
16
|
+
A class used to represent ORDS API
|
|
17
|
+
'''
|
|
18
|
+
def __init__(self, rest : Rest) -> None:
|
|
19
|
+
self.set_rest(rest)
|
|
20
|
+
self.Analytics = self.InAnalytics(rest)
|
|
21
|
+
self.Ingest = self.InIngest(rest)
|
|
22
|
+
self.Insight = self.InInsight(rest)
|
|
23
|
+
self.Misc = self.InMisc(rest)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InAnalytics(AdpAnalytics):
|
|
27
|
+
'''
|
|
28
|
+
Class for analytic view
|
|
29
|
+
'''
|
|
30
|
+
def __init__(self, rest : Rest) -> None:
|
|
31
|
+
super().__init__()
|
|
32
|
+
super().set_rest(rest)
|
|
33
|
+
|
|
34
|
+
class InIngest(AdpIngest):
|
|
35
|
+
'''
|
|
36
|
+
Class for copy tables from db link or cloud storage
|
|
37
|
+
'''
|
|
38
|
+
def __init__(self, rest : Rest) -> None:
|
|
39
|
+
super().__init__()
|
|
40
|
+
super().set_rest(rest)
|
|
41
|
+
|
|
42
|
+
class InInsight(AdpInsight):
|
|
43
|
+
'''
|
|
44
|
+
Class for insights
|
|
45
|
+
'''
|
|
46
|
+
def __init__(self, rest : Rest) -> None:
|
|
47
|
+
super().__init__()
|
|
48
|
+
super().set_rest(rest)
|
|
49
|
+
|
|
50
|
+
class InMisc(AdpMisc):
|
|
51
|
+
'''
|
|
52
|
+
Class for additional functions
|
|
53
|
+
'''
|
|
54
|
+
def __init__(self, rest : Rest) -> None:
|
|
55
|
+
super().__init__()
|
|
56
|
+
super().set_rest(rest)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def set_rest(self, rest : Rest) -> None:
|
|
60
|
+
'''
|
|
61
|
+
Set REST class
|
|
62
|
+
'''
|
|
63
|
+
|
|
64
|
+
self.rest = rest
|
|
65
|
+
|
|
66
|
+
def get_rest(self) -> Rest:
|
|
67
|
+
'''
|
|
68
|
+
Access to Rest class
|
|
69
|
+
'''
|
|
70
|
+
return self.rest
|
adp/adp_analytics.py
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2023-2024, Oracle and/or its affiliates.
|
|
5
|
+
|
|
6
|
+
'''
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
from .rest import Rest
|
|
11
|
+
from .rest import ThreadWithResult
|
|
12
|
+
from .adp_misc import AdpMisc
|
|
13
|
+
|
|
14
|
+
class AdpAnalytics(object):
|
|
15
|
+
'''
|
|
16
|
+
classdocs
|
|
17
|
+
'''
|
|
18
|
+
def __init__(self):
|
|
19
|
+
'''
|
|
20
|
+
Constructor
|
|
21
|
+
'''
|
|
22
|
+
self.utils = AdpMisc()
|
|
23
|
+
self.rest=None
|
|
24
|
+
|
|
25
|
+
def set_rest(self, rest: Rest):
|
|
26
|
+
'''
|
|
27
|
+
Set Rest instance
|
|
28
|
+
|
|
29
|
+
@param rest (Rest): rest instance
|
|
30
|
+
'''
|
|
31
|
+
self.rest = rest
|
|
32
|
+
self.utils.set_rest(rest)
|
|
33
|
+
|
|
34
|
+
def get_list(self, owner : str = None) -> str:
|
|
35
|
+
'''
|
|
36
|
+
Gel list of Analytic Views
|
|
37
|
+
@param owner (String): schema name of the analytic views (None means that the current schema is used)
|
|
38
|
+
'''
|
|
39
|
+
if owner is None:
|
|
40
|
+
owner = self.rest.username
|
|
41
|
+
search_string = "( owner: {0} ) ( type: ANALYTIC_VIEW ) ( application: DATABASE )".format(owner)
|
|
42
|
+
return self.utils.global_search(search_string=search_string, rowstart=1, numrow=50,hide_system_tables=True, hide_private_tables=True, resultapp="ADPINS")
|
|
43
|
+
|
|
44
|
+
def drop(self, model_name : str, delete_objects : bool = True) -> str:
|
|
45
|
+
'''
|
|
46
|
+
Drop the Analytic view
|
|
47
|
+
|
|
48
|
+
@param model_name (String): name of the Analytic view
|
|
49
|
+
@param delete_objects (boolean): Delete object
|
|
50
|
+
'''
|
|
51
|
+
if self.is_exist(model_name):
|
|
52
|
+
delete="FALSE"
|
|
53
|
+
if delete_objects:
|
|
54
|
+
delete = "TRUE"
|
|
55
|
+
url = "{0}/_adpavd/_services/objects/dropavmodel/".format(self.rest.get_prefix())
|
|
56
|
+
data={"name": model_name, "deleteObjects": delete}
|
|
57
|
+
return self.rest.post(url, data)
|
|
58
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
59
|
+
|
|
60
|
+
def get_measures_list(self, av_name : str, owner : str = None) -> str:
|
|
61
|
+
'''
|
|
62
|
+
Get list of measures for Analytic view
|
|
63
|
+
@param av_name (String): name of the Analytic view
|
|
64
|
+
@param owner (String): schema name of the analytic view (None means that the current schema is used)
|
|
65
|
+
'''
|
|
66
|
+
if owner is None:
|
|
67
|
+
owner = self.rest.username
|
|
68
|
+
if self.is_exist(av_name, owner):
|
|
69
|
+
url = "{0}/_adpins/_services/objects/entityhierarchy/?entityschema={1}&entitytype=MEASURE&parentpath=\"DB\".\"{2}\"".format(self.rest.get_prefix(), self.rest.encode(owner), self.rest.encode(av_name))
|
|
70
|
+
return self.rest.get(url)
|
|
71
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_data_preview(self, entity_name : str, owner : str = None) ->str:
|
|
75
|
+
'''
|
|
76
|
+
Get data preview for the analytic view
|
|
77
|
+
@param entity_name (String): name of the Analytic view
|
|
78
|
+
@param owner (String): schema name of the analytic view (None means that the current schema is used)
|
|
79
|
+
'''
|
|
80
|
+
if owner is None:
|
|
81
|
+
owner = self.rest.username
|
|
82
|
+
if self.is_exist(entity_name, owner):
|
|
83
|
+
data = {"object":{ "owner": owner,"name": entity_name, "type": "ANALYTIC_VIEW"}, "metadata": {}, "results": { "metadata": True, "data": True }}
|
|
84
|
+
data_text = self.rest.stringify(data)
|
|
85
|
+
url = "{0}/_adpavd/_services/objects/avdata/?av_query_json={1}".format(self.rest.get_prefix(), self.rest.encode(data_text))
|
|
86
|
+
return self.rest.get(url)
|
|
87
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
88
|
+
|
|
89
|
+
def get_metadata(self, av_name : str, owner : str = None) -> str:
|
|
90
|
+
'''
|
|
91
|
+
Get Analytic view metadata
|
|
92
|
+
@param av_name (String): name of the analytic view
|
|
93
|
+
@param owner (String): schema name of the analytic view (None means that the current schema is used)
|
|
94
|
+
'''
|
|
95
|
+
if owner is None:
|
|
96
|
+
owner = self.rest.username
|
|
97
|
+
if self.is_exist(av_name, owner):
|
|
98
|
+
url = "{0}/_adpanalytics/_services/objects/getAVMetadata/?av_name={1}&owner={2}".format(self.rest.get_prefix(), av_name, owner)
|
|
99
|
+
return self.rest.get(url)
|
|
100
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_dimension_names(self,av_name : str) -> str:
|
|
104
|
+
'''
|
|
105
|
+
Get dimension names for the Analytic view
|
|
106
|
+
@param av_name (String): name of the analytic view
|
|
107
|
+
'''
|
|
108
|
+
|
|
109
|
+
if self.is_exist(av_name):
|
|
110
|
+
url = "{0}/_adpanalytics/_services/objects/getDimensionNames/?av_name={1}".format(self.rest.get_prefix(), av_name)
|
|
111
|
+
return self.rest.get(url)
|
|
112
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
113
|
+
|
|
114
|
+
def get_fact_table_name(self, av_name : str) -> str:
|
|
115
|
+
'''
|
|
116
|
+
Get name of fact table for the Analytic view
|
|
117
|
+
@param av_name (String): name of the Analytic view
|
|
118
|
+
'''
|
|
119
|
+
|
|
120
|
+
if self.is_exist(av_name):
|
|
121
|
+
url = "{0}/_adpanalytics/_services/objects/getFactTableNames/?av_name={1}".format(self.rest.get_prefix(), av_name)
|
|
122
|
+
return self.rest.get(url)
|
|
123
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
124
|
+
|
|
125
|
+
def get_error_classes_from_dim(self, av_name : str, dimension : str) -> str:
|
|
126
|
+
'''
|
|
127
|
+
Get error class for the dimension name for the Analytic view
|
|
128
|
+
@param av_name (String): name of the Analytic view
|
|
129
|
+
@param dimension (String): name of the dimension
|
|
130
|
+
'''
|
|
131
|
+
|
|
132
|
+
if self.is_exist(av_name):
|
|
133
|
+
get_details = {
|
|
134
|
+
"av_name": av_name,
|
|
135
|
+
"dimension": dimension
|
|
136
|
+
}
|
|
137
|
+
url = "{0}/_adpanalytics/_services/objects/getErrorClassesFromDim/?get_dim_details={1}".format(self.rest.get_prefix(), self.rest.encode(self.rest.stringify(get_details)) )
|
|
138
|
+
return self.rest.get(url)
|
|
139
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
140
|
+
|
|
141
|
+
def get_error_classes_from_fact_tab(self, av_name : str, fact_tab : str) -> str:
|
|
142
|
+
'''
|
|
143
|
+
Get error class for the fact table for the Analytic view
|
|
144
|
+
@param av_name (String): name of the Analytic view
|
|
145
|
+
@param factTab (String): name of the fact table
|
|
146
|
+
'''
|
|
147
|
+
|
|
148
|
+
if self.is_exist(av_name):
|
|
149
|
+
get_details = {
|
|
150
|
+
"av_name": av_name,
|
|
151
|
+
"fact_tab": fact_tab
|
|
152
|
+
}
|
|
153
|
+
url = "{0}/_adpanalytics/_services/objects/getErrorClassesFromFactTab/?get_tab_details={1}".format(self.rest.get_prefix(), self.rest.encode(self.rest.stringify(get_details)) )
|
|
154
|
+
return self.rest.get(url)
|
|
155
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
156
|
+
|
|
157
|
+
def quality_report(self, av_name: str) ->str:
|
|
158
|
+
'''
|
|
159
|
+
Get quality report
|
|
160
|
+
'''
|
|
161
|
+
if self.is_exist(av_name):
|
|
162
|
+
|
|
163
|
+
error_list = []
|
|
164
|
+
|
|
165
|
+
text = self.get_fact_table_name(av_name)
|
|
166
|
+
j = json.loads(text)
|
|
167
|
+
fact_table = j["FACT_TABLE_NAME"]
|
|
168
|
+
|
|
169
|
+
text = self.get_dimension_names(av_name)
|
|
170
|
+
j = json.loads(text)
|
|
171
|
+
|
|
172
|
+
dims = []
|
|
173
|
+
for dim in j:
|
|
174
|
+
dims.append(dim['DIMENSION_NAME'])
|
|
175
|
+
|
|
176
|
+
text = self.get_error_classes_from_fact_tab(av_name, fact_table)
|
|
177
|
+
j = json.loads(text)
|
|
178
|
+
count = j[0]['ERROR_COUNT']
|
|
179
|
+
if count == 0:
|
|
180
|
+
error_text = f'Fact table {fact_table} has no errors'
|
|
181
|
+
error_list.append(error_text)
|
|
182
|
+
else:
|
|
183
|
+
error_messages = []
|
|
184
|
+
error_data = j[0]['errorData']
|
|
185
|
+
for err_data in error_data:
|
|
186
|
+
error_messages.append(err_data['ERROR_MESSAGE'])
|
|
187
|
+
messages = ";".join(error_messages)
|
|
188
|
+
error_text = f'Fact table {fact_table} has {count} errors: {messages}'
|
|
189
|
+
error_list.append(error_text)
|
|
190
|
+
|
|
191
|
+
for dim in dims:
|
|
192
|
+
text = self.get_error_classes_from_dim(av_name, dim)
|
|
193
|
+
j = json.loads(text)
|
|
194
|
+
count = j[0]['ERROR_COUNT']
|
|
195
|
+
if count == 0:
|
|
196
|
+
error_text = f'Dimension {dim} has no errors'
|
|
197
|
+
error_list.append(error_text)
|
|
198
|
+
else:
|
|
199
|
+
error_messages = []
|
|
200
|
+
error_data = j[0]['errorData']
|
|
201
|
+
for err_data in error_data:
|
|
202
|
+
error_messages.append(err_data['ERROR_MESSAGE'])
|
|
203
|
+
messages = ";".join(error_messages)
|
|
204
|
+
error_text = f'Dimension {dim} has {count} errors: {messages}'
|
|
205
|
+
error_list.append(error_text)
|
|
206
|
+
|
|
207
|
+
return json.dumps(error_list)
|
|
208
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
209
|
+
|
|
210
|
+
def _get_payload(self, levels : bool, column_names : list, entity_name : str, hierarchies : list, measures : list, where_condition : list, owner = None) -> dict:
|
|
211
|
+
'''
|
|
212
|
+
Get payload for getting data
|
|
213
|
+
|
|
214
|
+
@param levels:
|
|
215
|
+
@param columnNames:
|
|
216
|
+
@param entityName:
|
|
217
|
+
@param hierarchies:
|
|
218
|
+
@param measures:
|
|
219
|
+
@param whereCondition:
|
|
220
|
+
@param owner (String): schema name of the entity (None means that the current schema is used)
|
|
221
|
+
'''
|
|
222
|
+
if owner is None:
|
|
223
|
+
owner = self.rest.username
|
|
224
|
+
return {
|
|
225
|
+
"visualspec": {"hierOrder": False,"addAllHiers": False, "addAllMeas": False, "hierAttributes": True,
|
|
226
|
+
"levels": levels, "columns": column_names},
|
|
227
|
+
"object": { "owner": owner, "name": entity_name, "type": "ANALYTIC_VIEW"},
|
|
228
|
+
"queryspec": { "hierarchies": hierarchies, "measures": measures,
|
|
229
|
+
"where": where_condition, "fromDepth": 1, "toDepth": 100},
|
|
230
|
+
"results": { "metadata": True, "data": True}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def get_data(self, levels : bool, column_names : list, entity_name : str, hierarchies : list, measures : list, where_condition : list, owner = None) -> str:
|
|
234
|
+
'''
|
|
235
|
+
Get data of the Analytic view
|
|
236
|
+
|
|
237
|
+
@param levels:
|
|
238
|
+
@param columnNames:
|
|
239
|
+
@param entityName:
|
|
240
|
+
@param hierarchies:
|
|
241
|
+
@param measures:
|
|
242
|
+
@param whereCondition:
|
|
243
|
+
@param owner (String): schema name of the enrtity (None means that the current schema is used)
|
|
244
|
+
'''
|
|
245
|
+
if self.is_exist(entity_name):
|
|
246
|
+
payload = self._get_payload(levels, column_names, entity_name, hierarchies, measures, where_condition, owner)
|
|
247
|
+
url = "{0}/_adpanalytics/_services/objects/getAVData/".format(self.rest.get_prefix())
|
|
248
|
+
#print(url)
|
|
249
|
+
return self.rest.post(url, payload)
|
|
250
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_sql(self, levels : bool, column_names : list, entity_name : str, hierarchies : list, measures : list, where_condition : list, owner = None) -> str:
|
|
254
|
+
'''
|
|
255
|
+
Get SQL query for data of the Analytic view
|
|
256
|
+
|
|
257
|
+
@param levels:
|
|
258
|
+
@param columnNames:
|
|
259
|
+
@param entityName:
|
|
260
|
+
@param hierarchies:
|
|
261
|
+
@param measures:
|
|
262
|
+
@param whereCondition:
|
|
263
|
+
@param owner (String): schema name of the entity (None means that the current schema is used)
|
|
264
|
+
'''
|
|
265
|
+
|
|
266
|
+
if self.is_exist(entity_name):
|
|
267
|
+
payload = self._get_payload(levels, column_names, entity_name, hierarchies, measures, where_condition, owner)
|
|
268
|
+
url = "{0}/_adpanalytics/_services/objects/getSQL/".format(self.rest.get_prefix())
|
|
269
|
+
return self.rest.post(url, payload)
|
|
270
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def compile(self, av_name : str, owner : str = None) -> str:
|
|
274
|
+
'''
|
|
275
|
+
Compile the Analytic view
|
|
276
|
+
|
|
277
|
+
@param avName (String): name of the Analytic view
|
|
278
|
+
@param owner (String): schema name of the fact table (None means that the current schema is used)
|
|
279
|
+
'''
|
|
280
|
+
if owner is None:
|
|
281
|
+
owner = self.rest.username
|
|
282
|
+
if self.is_exist(av_name, owner):
|
|
283
|
+
url = "{0}/_adpavd/_services/objects/compile_av/".format(self.rest.get_prefix())
|
|
284
|
+
data = { "name": av_name, "schema": owner }
|
|
285
|
+
return self.rest.post(url, data)
|
|
286
|
+
return "{\"message\": \"Analytic view does not exist\"}"
|
|
287
|
+
|
|
288
|
+
def generate_data(self, table : str, data_field : str, skip : str = None, prefix : str = "Data") -> Tuple[list, list]:
|
|
289
|
+
'''
|
|
290
|
+
Extract data from getAVData
|
|
291
|
+
|
|
292
|
+
@param table (String): payload of getAVData
|
|
293
|
+
@param dataField (String): name of the columns to extract
|
|
294
|
+
@param skip (String): true if skip null values
|
|
295
|
+
@param prefix (String): prefix for labels or column name in the query
|
|
296
|
+
'''
|
|
297
|
+
categories = []
|
|
298
|
+
data = []
|
|
299
|
+
i = 1
|
|
300
|
+
json_data = json.loads(table)
|
|
301
|
+
for json_line in json_data:
|
|
302
|
+
#print(json_line)
|
|
303
|
+
is_skip = True
|
|
304
|
+
if skip is None:
|
|
305
|
+
is_skip = False
|
|
306
|
+
elif json_line[skip] is not None:
|
|
307
|
+
is_skip = False
|
|
308
|
+
if not is_skip:
|
|
309
|
+
data.append(json_line[data_field])
|
|
310
|
+
if json_line.get(prefix) is None:
|
|
311
|
+
categories.append(prefix+str(i))
|
|
312
|
+
else:
|
|
313
|
+
categories.append(json_line[prefix])
|
|
314
|
+
i = i + 1
|
|
315
|
+
|
|
316
|
+
return categories, data
|
|
317
|
+
|
|
318
|
+
def create(self, fact_table : str, skip_find_dimensions : bool = False, owner : str = None) -> str:
|
|
319
|
+
'''
|
|
320
|
+
Create the Analytic view
|
|
321
|
+
|
|
322
|
+
@param factTable (String): fact table of the Analytic view
|
|
323
|
+
@param owner (String): schema name of the fact table (None means that the current schema is used)
|
|
324
|
+
'''
|
|
325
|
+
if owner is None:
|
|
326
|
+
owner = self.rest.username
|
|
327
|
+
parameters = {"factTable": fact_table, "owner": owner, "model": fact_table+"_MODEL",
|
|
328
|
+
"avModelName": "AV_" + fact_table, "avName": fact_table + "_AV",
|
|
329
|
+
"caption": fact_table.replace("_", " ") +" Analytic View",
|
|
330
|
+
"sources": None, "progressKey": None}
|
|
331
|
+
|
|
332
|
+
is_exists = self.is_exist(parameters["avName"], parameters["owner"])
|
|
333
|
+
if is_exists > 0:
|
|
334
|
+
av_name = parameters['avName']
|
|
335
|
+
message = f"Analytic View {av_name} already exists"
|
|
336
|
+
json_message = {'message': message}
|
|
337
|
+
return json_message
|
|
338
|
+
|
|
339
|
+
payload = self._generate_payload_for_lineage(parameters)
|
|
340
|
+
|
|
341
|
+
text = self._av_model_source_lineage(payload)
|
|
342
|
+
|
|
343
|
+
parameters["progressKey"] = self._get_progress_key()
|
|
344
|
+
|
|
345
|
+
if skip_find_dimensions:
|
|
346
|
+
parameters["sources"] = None
|
|
347
|
+
else:
|
|
348
|
+
parameters["sources"] = self._find_candidate_dims_for_fact(fact_table, owner)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
thread = ThreadWithResult(target=self._auto_analytic_view, args=(parameters,))
|
|
352
|
+
thread.run()
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
text = self.get_model("SYS$PROGRESS_INDICATOR", parameters["progressKey"], owner)
|
|
356
|
+
json_text = json.loads(text)
|
|
357
|
+
if "state" in json_text:
|
|
358
|
+
if json_text.get("state") == "COMPLETE":
|
|
359
|
+
break
|
|
360
|
+
time.sleep(5)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
json_text = json.loads(thread.get())
|
|
364
|
+
|
|
365
|
+
json_object = json_text["analyticViews"]
|
|
366
|
+
json_object["analyticViewName"] = parameters["avName"]
|
|
367
|
+
del json_text["avjson"]
|
|
368
|
+
avjson={"useAVName":True}
|
|
369
|
+
json_text["avjson"] = avjson
|
|
370
|
+
|
|
371
|
+
self._create_model(self.rest.stringify(json_text), parameters)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
return self.rest.stringify(json_text)
|
|
375
|
+
|
|
376
|
+
def _generate_payload_for_lineage(self, parameters : dict) -> dict:
|
|
377
|
+
'''
|
|
378
|
+
Generate payload for Lineage
|
|
379
|
+
|
|
380
|
+
@param parameters (*): parameters for payload generation
|
|
381
|
+
'''
|
|
382
|
+
data = {"analyticViews":
|
|
383
|
+
{"name":parameters["model"],"analyticViewName":parameters["avModelName"],
|
|
384
|
+
"caption":parameters["avModelName"],"description":parameters["avModelName"],
|
|
385
|
+
"project_code":parameters["model"],"project_id":None,
|
|
386
|
+
"hierarchies":[],"joins":[],"measures":[]},
|
|
387
|
+
"sources":[{"owner":parameters["owner"],"source":parameters["factTable"]}],
|
|
388
|
+
"avExtensions":[{"name":"AV_AUTONOMOUS_AGGREGATE_CACHE","value":"ENABLED"},
|
|
389
|
+
{"name":"AV_BASETABLE_QUERY_TRANSFORM","value":"DISABLED"},
|
|
390
|
+
{"name":"AV_TRANSPARENCY_VIEWS","value":"ENABLED"}],
|
|
391
|
+
"name":parameters["avName"],"project_code":parameters["avName"]}
|
|
392
|
+
return data
|
|
393
|
+
|
|
394
|
+
def _av_model_source_lineage(self, payload : dict) -> str:
|
|
395
|
+
'''
|
|
396
|
+
Start Lineage model source
|
|
397
|
+
|
|
398
|
+
@param payload (*): payload for lineage
|
|
399
|
+
'''
|
|
400
|
+
url = "{0}/_adpavd/_services/objects/avmodelsourcelineage/".format(self.rest.get_prefix())
|
|
401
|
+
data = {"model_json": self.rest.stringify(payload)}
|
|
402
|
+
return self.rest.post(url, data)
|
|
403
|
+
|
|
404
|
+
def _get_progress_key(self) -> str:
|
|
405
|
+
'''
|
|
406
|
+
Get progress key for crating the Analytic view
|
|
407
|
+
'''
|
|
408
|
+
url = "{0}/_adpavd/_services/progress_indicator/get_progress_key/".format(self.rest.get_prefix())
|
|
409
|
+
return self.rest.get(url)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _find_candidate_dims_for_fact(self, fact_table: str, owner : str) -> list:
|
|
413
|
+
'''
|
|
414
|
+
Find dimensions for the fact table
|
|
415
|
+
|
|
416
|
+
@param pfactTable (String): fact table of the Analytic view
|
|
417
|
+
@param owner (String): schema name of the fact table (None means that the current schema is used)
|
|
418
|
+
'''
|
|
419
|
+
url = "{0}/_adpavd/_services/objects/findcandidatedimsforfact/?schema={1}&facttable={2}".format(self.rest.get_prefix(), owner, fact_table)
|
|
420
|
+
text = self.rest.get(url)
|
|
421
|
+
tables = []
|
|
422
|
+
json_object = json.loads(text)
|
|
423
|
+
recommended = json_object.get('Recommended')
|
|
424
|
+
for item in recommended:
|
|
425
|
+
tables.append(item.get('DimTable'))
|
|
426
|
+
|
|
427
|
+
return tables
|
|
428
|
+
|
|
429
|
+
def _auto_analytic_view(self, parameters : dict) -> str:
|
|
430
|
+
'''
|
|
431
|
+
Start Lineage model source
|
|
432
|
+
|
|
433
|
+
@param parameters (*): additional parameters
|
|
434
|
+
'''
|
|
435
|
+
|
|
436
|
+
sources = None
|
|
437
|
+
if parameters["sources"] is not None:
|
|
438
|
+
sources = ",".join(parameters["sources"])
|
|
439
|
+
|
|
440
|
+
data = {"owner":parameters["owner"],"fact":parameters["factTable"], "sources":sources,
|
|
441
|
+
"accuracy":80,"name":parameters["avName"], "caption": parameters["caption"], "description":parameters["caption"],
|
|
442
|
+
"code":parameters["avName"], "progresskey":parameters["progressKey"], "crossschema":None}
|
|
443
|
+
|
|
444
|
+
url = "{0}/_adpavd/_services/autoav/".format(self.rest.get_prefix())
|
|
445
|
+
|
|
446
|
+
return self.rest.post(url, data, timeout=None)
|
|
447
|
+
|
|
448
|
+
def is_exist(self, av_name : str, owner : str = None)-> bool:
|
|
449
|
+
'''
|
|
450
|
+
Check that the Analytic View exists
|
|
451
|
+
|
|
452
|
+
@param parameters (*): additional parameters
|
|
453
|
+
'''
|
|
454
|
+
if owner is None:
|
|
455
|
+
owner = self.rest.username
|
|
456
|
+
|
|
457
|
+
url = "{0}/_adpavd/_services/objects/av_exists/?owner={1}&name={2}".format(self.rest.get_prefix(), owner, av_name)
|
|
458
|
+
text = self.rest.get(url)
|
|
459
|
+
json_text = json.loads(text)
|
|
460
|
+
json_item = json_text["items"][0]
|
|
461
|
+
count = json_item.get("count")
|
|
462
|
+
return count > 0
|
|
463
|
+
|
|
464
|
+
def _create_model(self, payload, parameters : dict) -> str:
|
|
465
|
+
'''
|
|
466
|
+
Create Lineage model
|
|
467
|
+
|
|
468
|
+
@param payload (*): payload for lineage
|
|
469
|
+
@param parameters (*): additional parameters
|
|
470
|
+
|
|
471
|
+
'''
|
|
472
|
+
data = {"json": payload, "owner": parameters["owner"], "name": parameters["avName"], "genav": "Y"}
|
|
473
|
+
url = "{0}/_adpavd/_services/objects/createmodel/".format(self.rest.get_prefix())
|
|
474
|
+
self.rest.post(url, data)
|
|
475
|
+
|
|
476
|
+
def get_model(self, entity_type : str, entity_name : str, owner : str) -> str:
|
|
477
|
+
'''
|
|
478
|
+
Get Lineage Model
|
|
479
|
+
@param entityName (String): name of the entity
|
|
480
|
+
@param entityType (String): type of the entity
|
|
481
|
+
@param owner (String): schema name of the fact table (None means that the current schema is used)
|
|
482
|
+
'''
|
|
483
|
+
url = "{0}/_adpavd/_services/objects/getmodel/?type={1}&name={2}&owner={3}".format(self.rest.get_prefix(), self.rest.encode(entity_type), self.rest.encode(entity_name), owner)
|
|
484
|
+
return self.rest.get(url)
|