Skip to content

Commit 64222bc

Browse files
committed
add ScaleDB client for ScaleDB databases
1 parent b9de89f commit 64222bc

3 files changed

Lines changed: 284 additions & 0 deletions

File tree

‎seatable_api/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .context import context
33
from .date_utils import dateutils
44
from .convert_airtable import AirtableConvertor
5+
from .scaledb import ScaleDB
56

67

78
Base = SeaTableAPI

‎seatable_api/exception.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ class BaseUnauthError(ConnectionError):
1010

1111
def __str__(self):
1212
return "The base has not been authorized"
13+
14+
15+
class ScaleDBAPIError(ConnectionError):
16+
17+
def __init__(self, message, status_code=None):
18+
super().__init__(message)
19+
self.status_code = status_code

‎seatable_api/scaledb.py‎

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import requests
2+
3+
from .exception import ScaleDBAPIError
4+
from .utils import parse_headers, parse_server_url
5+
6+
7+
def check_auth(func):
8+
9+
def wrapper(obj, *args, **kwargs):
10+
if not obj.is_authed:
11+
raise ScaleDBAPIError('ScaleDB has not been authenticated, call auth() first.')
12+
return func(obj, *args, **kwargs)
13+
return wrapper
14+
15+
16+
def parse_seadb_response(response):
17+
try:
18+
raw = response.json()
19+
except ValueError:
20+
raw = None
21+
data = raw if isinstance(raw, dict) else {}
22+
error = data.get('error_message') or data.get('error_msg')
23+
if response.status_code >= 400 or response.status_code < 200:
24+
raise ScaleDBAPIError(error or '%s %s' % (response.status_code, response.text), response.status_code)
25+
if data.get('success') is False or error:
26+
raise ScaleDBAPIError(error or 'request failed.', response.status_code)
27+
if not isinstance(raw, dict):
28+
raise ScaleDBAPIError('invalid response: %s' % response.text, response.status_code)
29+
return data
30+
31+
32+
class ScaleDB(object):
33+
"""ScaleDB API
34+
"""
35+
36+
def __init__(self, token, server_url):
37+
"""
38+
:param token: str
39+
:param server_url: str
40+
"""
41+
self.token = token
42+
self.server_url = parse_server_url(server_url)
43+
self.seadb_server_url = None
44+
self.jwt_token = None
45+
self.headers = None
46+
self.database_uuid = None
47+
self.database_name = None
48+
self.permission = None
49+
self.timeout = 30
50+
self.is_authed = False
51+
52+
def __str__(self):
53+
return '<ScaleDB Database [ %s ]>' % self.database_name
54+
55+
def auth(self):
56+
"""Auth to ScaleDB
57+
"""
58+
url = self.server_url + '/api/v2.1/seadb/app-access-token/'
59+
headers = parse_headers(self.token)
60+
response = requests.get(url, headers=headers, timeout=self.timeout)
61+
data = parse_seadb_response(response)
62+
63+
database_uuid = data.get('database_uuid')
64+
seadb_server = data.get('seadb_server')
65+
access_token = data.get('access_token')
66+
if not database_uuid or not seadb_server or not access_token:
67+
raise ScaleDBAPIError('auth response is incomplete.')
68+
69+
self.seadb_server_url = parse_server_url(seadb_server)
70+
self.jwt_token = access_token
71+
self.headers = {
72+
'Authorization': 'Bearer ' + self.jwt_token,
73+
'Content-Type': 'application/json',
74+
}
75+
self.database_uuid = database_uuid
76+
self.database_name = data.get('database_name')
77+
self.permission = data.get('permission')
78+
self.is_authed = True
79+
80+
def _query_url(self):
81+
return self.seadb_server_url + '/api/v1/' + self.database_uuid + '/query'
82+
83+
def _rows_url(self):
84+
return self.seadb_server_url + '/api/v1/' + self.database_uuid + '/rows'
85+
86+
def _tables_url(self):
87+
return self.seadb_server_url + '/api/v1/' + self.database_uuid + '/tables'
88+
89+
def _columns_url(self):
90+
return self.seadb_server_url + '/api/v1/' + self.database_uuid + '/columns'
91+
92+
def _column_options_url(self):
93+
return self.seadb_server_url + '/api/v1/' + self.database_uuid + '/column-options'
94+
95+
@check_auth
96+
def add_table(self, table_name):
97+
"""
98+
:param table_name: str
99+
:return: str, table id
100+
"""
101+
url = self._tables_url()
102+
json_data = {
103+
'table_name': table_name,
104+
}
105+
response = requests.post(url, json=json_data, headers=self.headers, timeout=self.timeout)
106+
data = parse_seadb_response(response)
107+
return data.get('table_id')
108+
109+
@check_auth
110+
def rename_table(self, table_name, new_table_name):
111+
"""
112+
:param table_name: str
113+
:param new_table_name: str
114+
:return: dict
115+
"""
116+
url = self._tables_url()
117+
json_data = {
118+
'table_name': table_name,
119+
'new_table_name': new_table_name,
120+
}
121+
response = requests.put(url, json=json_data, headers=self.headers, timeout=self.timeout)
122+
return parse_seadb_response(response)
123+
124+
@check_auth
125+
def delete_table(self, table_name):
126+
"""
127+
:param table_name: str
128+
:return: dict
129+
"""
130+
url = self._tables_url()
131+
json_data = {
132+
'table_name': table_name,
133+
}
134+
response = requests.delete(url, json=json_data, headers=self.headers, timeout=self.timeout)
135+
return parse_seadb_response(response)
136+
137+
@check_auth
138+
def insert_column(self, table_name, column_name, column_type, column_data=None):
139+
"""
140+
:param table_name: str
141+
:param column_name: str
142+
:param column_type: str, text/int64/float64/float32/bool/datetime/single-select/multiple-select/list
143+
:param column_data: dict
144+
:return: str, column key
145+
"""
146+
url = self._columns_url()
147+
json_data = {
148+
'table_name': table_name,
149+
'column_name': column_name,
150+
'column_type': column_type,
151+
}
152+
if column_data:
153+
json_data['column_data'] = column_data
154+
response = requests.post(url, json=json_data, headers=self.headers, timeout=self.timeout)
155+
data = parse_seadb_response(response)
156+
return data.get('column_key')
157+
158+
@check_auth
159+
def rename_column(self, table_name, column_name, new_column_name):
160+
"""
161+
:param table_name: str
162+
:param column_name: str
163+
:param new_column_name: str
164+
:return: dict
165+
"""
166+
url = self._columns_url()
167+
json_data = {
168+
'table_name': table_name,
169+
'column_name': column_name,
170+
'new_column_name': new_column_name,
171+
}
172+
response = requests.put(url, json=json_data, headers=self.headers, timeout=self.timeout)
173+
return parse_seadb_response(response)
174+
175+
@check_auth
176+
def delete_column(self, table_name, column_name):
177+
"""
178+
:param table_name: str
179+
:param column_name: str
180+
:return: dict
181+
"""
182+
url = self._columns_url()
183+
json_data = {
184+
'table_name': table_name,
185+
'column_name': column_name,
186+
}
187+
response = requests.delete(url, json=json_data, headers=self.headers, timeout=self.timeout)
188+
return parse_seadb_response(response)
189+
190+
@check_auth
191+
def add_column_option(self, table_name, column_name, option_name, option_data=None):
192+
"""
193+
:param table_name: str
194+
:param column_name: str
195+
:param option_name: str
196+
:param option_data: dict
197+
:return: str, option id
198+
"""
199+
url = self._column_options_url()
200+
json_data = {
201+
'table_name': table_name,
202+
'column_name': column_name,
203+
'option_name': option_name,
204+
}
205+
if option_data:
206+
json_data['option_data'] = option_data
207+
response = requests.post(url, json=json_data, headers=self.headers, timeout=self.timeout)
208+
data = parse_seadb_response(response)
209+
return data.get('option_id')
210+
211+
@check_auth
212+
def query(self, sql, convert=True, parameters=None):
213+
"""
214+
:param sql: str
215+
:param convert: bool
216+
:param parameters: list
217+
:return: list
218+
"""
219+
if not sql:
220+
raise ValueError('sql can not be empty.')
221+
url = self._query_url()
222+
json_data = {
223+
'sql': sql,
224+
'parameters': parameters or [],
225+
'convert_keys': bool(convert),
226+
}
227+
response = requests.post(url, json=json_data, headers=self.headers, timeout=self.timeout)
228+
data = parse_seadb_response(response)
229+
return data.get('results') or []
230+
231+
@check_auth
232+
def insert_rows(self, table_name, rows, replace=False):
233+
"""
234+
:param table_name: str
235+
:param rows: list
236+
:param replace: bool
237+
:return: dict
238+
"""
239+
url = self._rows_url()
240+
json_data = {
241+
'table_name': table_name,
242+
'rows': rows,
243+
'replace': replace,
244+
}
245+
response = requests.post(url, json=json_data, headers=self.headers, timeout=self.timeout)
246+
return parse_seadb_response(response)
247+
248+
@check_auth
249+
def update_rows(self, table_name, updates):
250+
"""
251+
:param table_name: str
252+
:param updates: list, [{'pk': int, 'row': dict}]
253+
:return: dict
254+
"""
255+
url = self._rows_url()
256+
json_data = {
257+
'table_name': table_name,
258+
'updates': updates,
259+
}
260+
response = requests.put(url, json=json_data, headers=self.headers, timeout=self.timeout)
261+
return parse_seadb_response(response)
262+
263+
@check_auth
264+
def delete_rows(self, table_name, pks):
265+
"""
266+
:param table_name: str
267+
:param pks: list
268+
:return: dict
269+
"""
270+
url = self._rows_url()
271+
json_data = {
272+
'table_name': table_name,
273+
'pks': pks,
274+
}
275+
response = requests.delete(url, json=json_data, headers=self.headers, timeout=self.timeout)
276+
return parse_seadb_response(response)

0 commit comments

Comments
 (0)