Skip to content

Commit ebbf0bb

Browse files
author
boonhapus
committed
Merge branch 'dev'
2 parents 9e87773 + a2068fe commit ebbf0bb

File tree

130 files changed

+117741
-114
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+117741
-114
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ __pycache__/
66

77
# Mac stuff
88
.DS_Stores/
9+
.DS_Store
910

1011
# C extensions
1112
*.so

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
include cs_tools/tools/**/static/*
1+
recursive-include cs_tools/tools/**/static *

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ From your command line:
3636
$ cd $HOME
3737
$ python3 -m venv .cs_tools-dev
3838
$ source .cs_tools-dev/bin/activate
39-
$ pip install -e git+https://github.com/thoughtspot/cs_tools.git
39+
$ pip install -e .
4040
```
4141

4242
That's it!

cs_tools/_version.py

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

cs_tools/api.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from cs_tools.models.user import User
1515
from cs_tools.schema.user import User as UserSchema
1616
from cs_tools.errors import CertificateVerifyFailure
17-
from cs_tools.const import APP_DIR
1817

1918

2019
log = logging.getLogger(__name__)
@@ -25,7 +24,6 @@ class ThoughtSpot:
2524
"""
2625
def __init__(self, ts_config):
2726
self.config = ts_config
28-
self._setup_logging()
2927

3028
# set up our session
3129
# NOTE: base_url is a valid parameter for httpx.Client
@@ -51,31 +49,6 @@ def __init__(self, ts_config):
5149
self._security = _Security(self)
5250
self._session = _Session(self)
5351

54-
def _clean_logs(self, now):
55-
logs_dir = APP_DIR / 'logs'
56-
logs_dir.mkdir(parents=True, exist_ok=True)
57-
58-
# keep only the last 25 logfiles
59-
lifo = sorted(logs_dir.iterdir(), reverse=True)
60-
61-
for idx, log in enumerate(lifo):
62-
if idx > 25:
63-
log.unlink()
64-
65-
def _setup_logging(self):
66-
logging.getLogger('urllib3').setLevel(logging.ERROR)
67-
68-
now = dt.datetime.now().strftime('%Y-%m-%dT%H_%M_%S')
69-
self._clean_logs(now)
70-
71-
logging.basicConfig(
72-
filename=f'{APP_DIR}/logs/{now}.log',
73-
format='[%(levelname)s - %(asctime)s] '
74-
'[%(name)s - %(module)s.%(funcName)s %(lineno)d] '
75-
'%(message)s',
76-
level=self.config.logging.level
77-
)
78-
7952
@property
8053
def host(self):
8154
"""

cs_tools/cli.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import datetime as dt
12
import pathlib
3+
import logging
24
import shutil
5+
import logging
36

47
from typer import Argument as A_, Option as O_
8+
from rich.logging import RichHandler
59
import pydantic
610
import click
711
import typer
@@ -11,6 +15,13 @@
1115
from cs_tools.helpers.loader import _gather_tools
1216
from cs_tools.util.algo import deep_update
1317
from cs_tools.settings import TSConfig
18+
from cs_tools.const import APP_DIR
19+
20+
21+
log = logging.getLogger(__name__)
22+
23+
24+
log = logging.getLogger(__name__)
1425

1526

1627
app = typer.Typer(
@@ -105,7 +116,7 @@ def export(
105116
app_dir = pathlib.Path(typer.get_app_dir('cs_tools'))
106117
log_dir = app_dir / 'logs'
107118

108-
for log in log_dir.glob('*.tml'):
119+
for log in log_dir.iterdir():
109120
shutil.copy(log, save_path)
110121

111122

@@ -227,6 +238,18 @@ def delete(
227238
console.print(f'removed cluster configuration file "{name}"')
228239

229240

241+
def _clean_logs(now):
242+
logs_dir = APP_DIR / 'logs'
243+
logs_dir.mkdir(parents=True, exist_ok=True)
244+
245+
# keep only the last 25 logfiles
246+
lifo = sorted(logs_dir.iterdir(), reverse=True)
247+
248+
for idx, log in enumerate(lifo):
249+
if idx > 25:
250+
log.unlink()
251+
252+
230253
def run():
231254
"""
232255
Entrypoint into cs_tools.
@@ -236,10 +259,28 @@ def run():
236259
app.add_typer(cfg_app, name='config')
237260
app.add_typer(log_app, name='logs')
238261

262+
# SETUP LOGGING
263+
logging.getLogger('urllib3').setLevel(logging.ERROR)
264+
265+
now = dt.datetime.now().strftime('%Y-%m-%dT%H_%M_%S')
266+
_clean_logs(now)
267+
268+
logging.basicConfig(
269+
filename=f'{APP_DIR}/logs/{now}.log',
270+
format='[%(levelname)s - %(asctime)s] '
271+
'[%(name)s - %(module)s.%(funcName)s %(lineno)d] '
272+
'%(message)s',
273+
level='DEBUG'
274+
)
275+
239276
try:
240277
app(prog_name='cs_tools')
241278
except Exception as e:
279+
log.debug('whoopsie, something went wrong!', exc_info=True)
280+
242281
if hasattr(e, 'warning'):
243282
e = e.warning
283+
else:
284+
e = f'{type(e).__name__}: {e}'
244285

245-
console.print(f'[error]{e}')
286+
log.exception(f'[error]{e}')

cs_tools/models/_base.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import copy
23
import json
34

45
import httpx
@@ -23,6 +24,9 @@ def base_url(self):
2324
host = self.config.thoughtspot.host
2425
port = self.config.thoughtspot.port
2526

27+
if not host.startswith('http'):
28+
host = f'https://{host}'
29+
2630
if port:
2731
port = f':{port}'
2832
else:
@@ -45,9 +49,13 @@ def _request(self, method, url, *args, **kwargs) -> httpx.Response:
4549
# sigh/
4650

4751
# don't log the password
48-
secure = kwargs.copy()
49-
secure.pop('password', None)
50-
log.debug(f'>> {method} to {url} with data:\nargs={args}\nkwargs={secure}')
52+
try:
53+
secure = copy.deepcopy(kwargs)
54+
except TypeError:
55+
secure = copy.deepcopy({k: v for k, v in kwargs.items() if k not in ('file', 'files')})
56+
57+
secure.get('data', {}).pop('password', None)
58+
log.debug(f'>> {method} to {url} with data:\n\targs={args}\n\tkwargs={secure}')
5159

5260
r = self.http.request(method, url, *args, **kwargs)
5361
log.debug(f'<< {r.status_code} from {url}')

cs_tools/models/metadata.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
import logging
33
import enum
44

5+
import pydantic
56
import httpx
67

8+
from cs_tools.util.swagger import to_array
79
from cs_tools.settings import APIParameters
810
from cs_tools.models import TSPrivate, TSPublic
911

@@ -70,7 +72,7 @@ class ListVizHeadersParameters(APIParameters):
7072

7173
class ListObjectHeadersParameters(APIParameters):
7274
type: Union[MetadataObject, None] = MetadataObject.PINBOARD_ANSWER_BOOK
73-
subtypes: LogicalTableSubtype = None
75+
subtypes: List[LogicalTableSubtype] = None
7476
category: MetadataCategory = MetadataCategory.ALL
7577
sort: SortOrder = SortOrder.DEFAULT
7678
sortascending: bool = None
@@ -83,6 +85,10 @@ class ListObjectHeadersParameters(APIParameters):
8385
fetchids: str = None
8486
auto_created: bool = None
8587

88+
@pydantic.validator('subtypes')
89+
def stringify_the_array(cls, v):
90+
return to_array([_.value for _ in v])
91+
8692

8793
class ListParameters(ListObjectHeadersParameters):
8894
ownertypes: LogicalTableSubtype = None
@@ -106,6 +112,21 @@ class DetailParameters(APIParameters):
106112
doUpdate: bool = True
107113

108114

115+
class ListColumnParameters(APIParameters):
116+
id: str
117+
showhidden: bool = False
118+
119+
120+
class DeleteParameters(APIParameters):
121+
type: MetadataObject = None
122+
id: str
123+
includedisabled: bool = False
124+
125+
@pydantic.validator('id', pre=True)
126+
def stringify_the_array(cls, v) -> str:
127+
return to_array(v)
128+
129+
109130
#
110131

111132
class Metadata(TSPublic):
@@ -167,16 +188,32 @@ def list(self, **parameters) -> httpx.Response:
167188

168189
def listas(self, **parameters) -> httpx.Response:
169190
"""
170-
TODO
191+
List of metadata objects in the repository as seen by a User/Group.
171192
"""
172193
p = ListAsParameters(**parameters)
173194
r = self.get(f'{self.base_url}/listas', params=p.json())
174195
return r
175196

176197
def detail(self, guid, **parameters) -> httpx.Response:
177198
"""
178-
TODO
199+
Detail of a metadata object in the repository.
179200
"""
180201
p = DetailParameters(id=guid, **parameters)
181202
r = self.get(f'{self.base_url}/detail/{guid}', params=p.json())
182203
return r
204+
205+
def delete(self, **parameters) -> httpx.Response:
206+
"""
207+
Delete metadata object(s) from the repository.
208+
"""
209+
p = DeleteParameters(**parameters)
210+
r = self.post(f'{self.base_url}/delete', data=p.json())
211+
return r
212+
213+
def list_columns(self, guid, **parameters) -> httpx.Response:
214+
"""
215+
Get list of all logical columns of a given logical table.
216+
"""
217+
p = ListColumnParameters(id=guid, **parameters)
218+
r = self.get(f'{self.base_url}/listcolumns/{guid}', params=p.json())
219+
return r

cs_tools/models/periscope.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ def base_url(self):
2525
host = self.config.thoughtspot.host
2626
port = self.config.thoughtspot.port
2727

28+
if not host.startswith('http'):
29+
host = f'https://{host}'
30+
2831
if port:
2932
port = f':{port}'
3033
else:

cs_tools/models/security.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import enum
1+
from typing import List, Dict
22
import pydantic
33
import httpx
4-
from typing import List
4+
import enum
55

6+
from cs_tools.util.swagger import to_array
67
from cs_tools.settings import APIParameters
78
from cs_tools.models import TSPrivate
89

910

11+
import logging
12+
log = logging.getLogger(__name__)
13+
14+
1015
class ObjectType(enum.Enum):
16+
LOGICAL_COLUMN = "LOGICAL_COLUMN" # table-, worksheet-, view-columns
1117
LOGICAL_TABLE = "LOGICAL_TABLE" # tables, worksheets, views
1218
QUESTION_ANSWER_BOOK = "QUESTION_ANSWER_BOOK" # answer
1319
PINBOARD_ANSWER_BOOK = "PINBOARD_ANSWER_BOOK" # pinboard
1420

1521

16-
class SharePermission(str, enum.Enum):
22+
class SharePermission(enum.Enum):
1723
MODIFY = "MODIFY"
1824
NO_ACCESS = "NO_ACCESS"
1925
READ_ONLY = "READ_ONLY"
@@ -23,27 +29,51 @@ def __str__(self):
2329

2430

2531
class ShareParameters(APIParameters):
32+
# NOTE: FORMAT OF .permission
33+
#
34+
# {
35+
# 'permissions': {
36+
# guid: {
37+
# 'shareMode': permission
38+
# }
39+
# }
40+
# }
41+
#
2642
type: ObjectType = None
2743
id: str = None
28-
# permission: Dict[str, Dict[str, Dict[str, SharePermission]]] = {}
29-
permission: str = ""
44+
permission: Dict[str, Dict[str, Dict[str, SharePermission]]]
45+
# permission: str = ""
3046
emailshares: List[pydantic.EmailStr] = []
3147
notify: bool = True
3248
message: str = ""
3349

34-
# TODO is it possible to use util.to_array()?
3550
@pydantic.validator('id', pre=True)
36-
def _flatten_id_list(cls, v):
37-
if not isinstance(id, str):
38-
v = ",".join(v)
39-
return f"[{v}]"
51+
def stringify_the_array(cls, v) -> str:
52+
return to_array(v)
4053

4154
@pydantic.validator('permission', pre=True)
42-
def _inject_share_mode(cls, v):
55+
def inject_share_mode(cls, v):
56+
if 'permissions' in v:
57+
v = v['permissions']
58+
4359
new_v = {}
44-
for group_id, permission in v.items():
45-
new_v[group_id] = {'shareMode': str(permission)}
46-
return str({'permissions': new_v})
60+
61+
for group_guid, permission in v.items():
62+
if 'shareMode' not in permission:
63+
permission = {'shareMode': permission}
64+
65+
new_v[group_guid] = permission
66+
67+
return {'permissions': new_v}
68+
69+
70+
class DefinedPermissionParameters(APIParameters):
71+
type: str
72+
id: str
73+
74+
@pydantic.validator('id', pre=True)
75+
def stringify_the_array(cls, v):
76+
return to_array(v)
4777

4878

4979
class _Security(TSPrivate):
@@ -63,5 +93,14 @@ def share(self, **parameters) -> httpx.Response:
6393
List of metadata objects in the repository.
6494
"""
6595
p = ShareParameters(**parameters)
96+
# print(p.json())
6697
r = self.post(f'{self.base_url}/share', data=p.json())
6798
return r
99+
100+
def defined_permission(self, **parameters) -> httpx.Response:
101+
"""
102+
Get defined permissions information for a given list of objects
103+
"""
104+
p = DefinedPermissionParameters(**parameters)
105+
r = self.post(f'{self.base_url}/definedpermission', data=p.json())
106+
return r

0 commit comments

Comments
 (0)