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 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)