Skip to content

Commit b917a04

Browse files
authored
Merge pull request #32 from thoughtspot/1.8.4
1.8.4 updates.
2 parents 5eb678b + a4476bf commit b917a04

File tree

5 files changed

+465
-13
lines changed

5 files changed

+465
-13
lines changed

examples_v2/set_obj_id.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import json
2+
import os
3+
import requests.exceptions
4+
from typing import Optional, Dict, List
5+
from urllib import parse
6+
import re
7+
from collections import Counter
8+
9+
from src.thoughtspot_rest_api_v1 import TSRestApiV2, TSTypesV2, ReportTypes, TSRestApiV1
10+
11+
username = os.getenv('username') # or type in yourself
12+
password = os.getenv('password') # or type in yourself
13+
server = os.getenv('server') # or type in yourself
14+
org_id = 0
15+
16+
ts = TSRestApiV2(server_url=server)
17+
try:
18+
auth_token_response = ts.auth_token_full(username=username, password=password,
19+
validity_time_in_sec=3000, org_id=org_id)
20+
ts.bearer_token = auth_token_response['token']
21+
except requests.exceptions.HTTPError as e:
22+
print(e)
23+
print(e.response.content)
24+
exit()
25+
26+
def create_obj_id_update_request(guid: str, obj_id: str):
27+
update_req = {
28+
"headers_update":
29+
(
30+
{'identifier': guid,
31+
'attributes': (
32+
{
33+
'name': 'obj_id',
34+
'value': obj_id
35+
}
36+
)
37+
}
38+
)
39+
}
40+
return update_req
41+
42+
# { 'guid' : 'obj_id' }
43+
def create_multi_obj_id_update_request(guid_obj_id_map: Dict):
44+
update_req = {
45+
"headers_update": []
46+
}
47+
for guid in guid_obj_id_map:
48+
header_item = {
49+
'identifier': guid,
50+
'attributes': (
51+
{
52+
'name': 'obj_id',
53+
'value': guid_obj_id_map[guid]
54+
}
55+
)
56+
}
57+
update_req["headers_update"].append(header_item)
58+
59+
return update_req
60+
61+
def set_one_object():
62+
# Simple example of setting a Table object to have a Full Qualified Name as the obj_id
63+
update_req = create_obj_id_update_request(guid='43ab8a16-473a-44dc-9c78-4346eeb51f6c', obj_id='Conn.DB_Name.TableName')
64+
65+
resp = ts.metadata_headers_update(request=update_req)
66+
print(json.dumps(resp, indent=2))
67+
68+
69+
def export_tml_with_obj_id(guid:Optional[str] = None,
70+
obj_id: Optional[str] = None,
71+
save_to_disk=True):
72+
# Example of metadata search using obj_identifier (the property may be updated?)
73+
if obj_id is not None:
74+
search_req = {
75+
"metadata": (
76+
{'obj_identifier': obj_id}
77+
),
78+
"sort_options": {
79+
"field_name": "CREATED",
80+
"order": "DESC"
81+
}
82+
}
83+
84+
tables = ts.metadata_search(request=search_req)
85+
if len(tables) == 1:
86+
guid = tables[0]['metadata_id']
87+
obj_id = tables[0]['metadata_header']['objId']
88+
89+
# print(json.dumps(log_tables, indent=2))
90+
91+
if guid is None:
92+
raise Exception()
93+
94+
# export_options allow shifting TML export to obj_id, without any guid references
95+
exp_opt = {
96+
"include_obj_id_ref": True,
97+
"include_guid": False,
98+
"include_obj_id": True
99+
}
100+
101+
102+
yaml_tml = ts.metadata_tml_export(metadata_ids=[guid], edoc_format='YAML',
103+
export_options=exp_opt)
104+
105+
if save_to_disk is True:
106+
print(yaml_tml[0]['edoc'])
107+
print("-------")
108+
109+
# Save the file with {obj_id}.{type}.{tml}
110+
filename = "{}.table.tml".format(obj_id)
111+
with open(file=filename, mode='w') as f:
112+
f.write(yaml_tml[0]['edoc'])
113+
114+
return yaml_tml
115+
116+
def retrieve_dev_org_objects_for_mapping(org_name: Optional[str] = None, org_id: Optional[int] = None):
117+
118+
if org_id is None:
119+
org_req = {
120+
"org_identifier": org_name
121+
}
122+
org_resp = ts.orgs_search(request=org_req)
123+
if len(org_resp) == 1:
124+
org_id = org_resp[0]["id"]
125+
else:
126+
raise Exception("No org with that org_name was found, please try again or provide org_id")
127+
128+
ts2 = TSRestApiV2(server_url=server)
129+
try:
130+
auth_token_response = ts2.auth_token_full(username=username, password=password,
131+
validity_time_in_sec=3000, org_id=org_id)
132+
ts.bearer_token = auth_token_response['token']
133+
except requests.exceptions.HTTPError as e:
134+
print(e)
135+
print(e.response.content)
136+
exit()
137+
138+
types = ["LOGICAL_TABLE", "LIVEBOARD", "ANSWER"]
139+
search_req = {
140+
"record_offset": 0,
141+
"record_size": -1,
142+
"include_headers": True,
143+
"include_details": True,
144+
"metadata":[
145+
{"type": "LOGICAL_TABLE"},{"type": "LIVEBOARD"},{"type": "ANSWER"}
146+
]
147+
,
148+
"sort_options": {
149+
"field_name": "CREATED",
150+
"order": "DESC"
151+
}
152+
}
153+
154+
conn_req = {
155+
"record_offset": 0,
156+
"record_size": -1,
157+
"include_headers": False,
158+
"include_details": False,
159+
"metadata": [
160+
{
161+
"type": "CONNECTION"
162+
}
163+
]
164+
,
165+
"sort_options": {
166+
"field_name": "CREATED",
167+
"order": "DESC"
168+
}
169+
}
170+
171+
# Tables - split out Worksheets/ Models / Views from actual Table Objects
172+
try:
173+
tables_resp = ts2.metadata_search(request=search_req)
174+
except requests.exceptions.HTTPError as e:
175+
print(e)
176+
print(e.response.content)
177+
exit()
178+
179+
# Connection names for mapping and use in obj_id naming schema for Tables
180+
try:
181+
conn_resp = ts2.metadata_search(request=conn_req)
182+
except requests.exceptions.HTTPError as e:
183+
print(e)
184+
print(e.response.content)
185+
exit()
186+
187+
conn_map = {}
188+
for conn in conn_resp:
189+
# Create URL safe portion of obj_id for Connection
190+
# Assuming No Duplicate Connection Names in Org (fix this first if you don't have)
191+
# Define whatever automatic transformations to create URL safe
192+
# but aesthetically pleasing first transform from the existing object names
193+
c_obj_id = conn["metadata_name"].replace(" ", "")
194+
c_obj_id = parse.quote(c_obj_id)
195+
# obj_id = obj_id.replace("%3A", "_")
196+
# After parse quoting, there characters are in form %XX , replace with _ or blank space
197+
c_obj_id = re.sub(r"%..", "", c_obj_id)
198+
conn_map[conn["metadata_id"]] = c_obj_id
199+
# print(json.dumps(tables_resp, indent=2))
200+
201+
final_guid_obj_id_map = {}
202+
203+
for table in tables_resp:
204+
ds_type = table["metadata_header"]["type"]
205+
206+
guid = table["metadata_id"]
207+
208+
# Special property for certain system items that exist across all orgs - skip, cannot reset except in Org 0
209+
if "belongToAllOrgs" in table["metadata_header"]:
210+
if table["metadata_header"]["belongToAllOrgs"] is True:
211+
continue
212+
213+
# Real tables
214+
if ds_type in ['ONE_TO_ONE_LOGICAL']:
215+
detail = table["metadata_detail"]
216+
db_table_details = detail["logicalTableContent"]["tableMappingInfo"]
217+
218+
# Assumes a "{db}__{schema}__{tableName}" naming convention, but
219+
# {tsConnection}__{table} may make more sense across a number of Orgs with identical 'schemas' with
220+
# differing names
221+
# Essentially you want identical, unique obj_id for "the same table" across Orgs
222+
obj_id = "{}__{}__{}".format(db_table_details["databaseName"], db_table_details["schemaName"],
223+
db_table_details["tableName"])
224+
else:
225+
# For non-table objects, obj_ids just need to be URL safe strings
226+
# This is an example of a basic transformation from the Display Name to a URL safe string
227+
obj_id = table["metadata_name"].replace(" ", "_") # Need more transformation
228+
obj_id = parse.quote(obj_id)
229+
# After parse quoting, there characters are in form %XX , replace with _ or blank space
230+
obj_id = re.sub(r"%..", "", obj_id)
231+
232+
final_guid_obj_id_map[guid] = obj_id
233+
234+
# print(json.dumps(final_guid_obj_id_map, indent=2))
235+
236+
return final_guid_obj_id_map
237+
238+
def find_duplicate_obj_ids(initial_map: Dict) -> Dict:
239+
cnt = Counter(initial_map.values())
240+
241+
if len(initial_map) == len(cnt):
242+
return {}
243+
else:
244+
duplicate_obj_ids = []
245+
for c in cnt:
246+
if cnt[c] > 1:
247+
duplicate_obj_ids.append(c)
248+
dup_map = {}
249+
250+
for m in initial_map:
251+
if initial_map[m] in duplicate_obj_ids:
252+
dup_map[m] = initial_map[m]
253+
return dup_map

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
requests-toolbelt

setup.cfg

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = thoughtspot_rest_api_v1
3-
version = 1.8.2
3+
version = 1.8.4
44
description = Library implementing the ThoughtSpot V1 REST API
55
long_description = file: README.md
66
long_description_content_type = text/markdown
@@ -26,8 +26,6 @@ packages = find:
2626
python_requires = >=3.6
2727
install_requires =
2828
requests
29-
PyYAML
30-
oyaml
3129
requests_toolbelt
3230

3331

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.8.2'
1+
__version__ = '1.8.4'

0 commit comments

Comments
 (0)