singlestoredb 1.16.1__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.
- singlestoredb/__init__.py +75 -0
- singlestoredb/ai/__init__.py +2 -0
- singlestoredb/ai/chat.py +139 -0
- singlestoredb/ai/embeddings.py +128 -0
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/apps/__init__.py +3 -0
- singlestoredb/apps/_cloud_functions.py +90 -0
- singlestoredb/apps/_config.py +72 -0
- singlestoredb/apps/_connection_info.py +18 -0
- singlestoredb/apps/_dashboards.py +47 -0
- singlestoredb/apps/_process.py +32 -0
- singlestoredb/apps/_python_udfs.py +100 -0
- singlestoredb/apps/_stdout_supress.py +30 -0
- singlestoredb/apps/_uvicorn_util.py +36 -0
- singlestoredb/auth.py +245 -0
- singlestoredb/config.py +484 -0
- singlestoredb/connection.py +1487 -0
- singlestoredb/converters.py +950 -0
- singlestoredb/docstring/__init__.py +33 -0
- singlestoredb/docstring/attrdoc.py +126 -0
- singlestoredb/docstring/common.py +230 -0
- singlestoredb/docstring/epydoc.py +267 -0
- singlestoredb/docstring/google.py +412 -0
- singlestoredb/docstring/numpydoc.py +562 -0
- singlestoredb/docstring/parser.py +100 -0
- singlestoredb/docstring/py.typed +1 -0
- singlestoredb/docstring/rest.py +256 -0
- singlestoredb/docstring/tests/__init__.py +1 -0
- singlestoredb/docstring/tests/_pydoctor.py +21 -0
- singlestoredb/docstring/tests/test_epydoc.py +729 -0
- singlestoredb/docstring/tests/test_google.py +1007 -0
- singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
- singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
- singlestoredb/docstring/tests/test_parser.py +248 -0
- singlestoredb/docstring/tests/test_rest.py +547 -0
- singlestoredb/docstring/tests/test_util.py +70 -0
- singlestoredb/docstring/util.py +141 -0
- singlestoredb/exceptions.py +120 -0
- singlestoredb/functions/__init__.py +16 -0
- singlestoredb/functions/decorator.py +201 -0
- singlestoredb/functions/dtypes.py +1793 -0
- singlestoredb/functions/ext/__init__.py +1 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +2133 -0
- singlestoredb/functions/ext/json.py +420 -0
- singlestoredb/functions/ext/mmap.py +413 -0
- singlestoredb/functions/ext/rowdat_1.py +724 -0
- singlestoredb/functions/ext/timer.py +89 -0
- singlestoredb/functions/ext/utils.py +218 -0
- singlestoredb/functions/signature.py +1578 -0
- singlestoredb/functions/typing/__init__.py +41 -0
- singlestoredb/functions/typing/numpy.py +20 -0
- singlestoredb/functions/typing/pandas.py +2 -0
- singlestoredb/functions/typing/polars.py +2 -0
- singlestoredb/functions/typing/pyarrow.py +2 -0
- singlestoredb/functions/utils.py +421 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +916 -0
- singlestoredb/fusion/handlers/__init__.py +0 -0
- singlestoredb/fusion/handlers/export.py +525 -0
- singlestoredb/fusion/handlers/files.py +690 -0
- singlestoredb/fusion/handlers/job.py +660 -0
- singlestoredb/fusion/handlers/models.py +250 -0
- singlestoredb/fusion/handlers/stage.py +502 -0
- singlestoredb/fusion/handlers/utils.py +324 -0
- singlestoredb/fusion/handlers/workspace.py +956 -0
- singlestoredb/fusion/registry.py +249 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/http/connection.py +1267 -0
- singlestoredb/magics/__init__.py +34 -0
- singlestoredb/magics/run_personal.py +137 -0
- singlestoredb/magics/run_shared.py +134 -0
- singlestoredb/management/__init__.py +9 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +462 -0
- singlestoredb/management/export.py +295 -0
- singlestoredb/management/files.py +1102 -0
- singlestoredb/management/inference_api.py +105 -0
- singlestoredb/management/job.py +887 -0
- singlestoredb/management/manager.py +373 -0
- singlestoredb/management/organization.py +226 -0
- singlestoredb/management/region.py +169 -0
- singlestoredb/management/utils.py +423 -0
- singlestoredb/management/workspace.py +1927 -0
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +2032 -0
- singlestoredb/mysql/constants/CLIENT.py +38 -0
- singlestoredb/mysql/constants/COMMAND.py +32 -0
- singlestoredb/mysql/constants/CR.py +78 -0
- singlestoredb/mysql/constants/ER.py +474 -0
- singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +896 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +450 -0
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/mysql/tests/base.py +126 -0
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/mysql/tests/test_DictCursor.py +132 -0
- singlestoredb/mysql/tests/test_SSCursor.py +141 -0
- singlestoredb/mysql/tests/test_basic.py +452 -0
- singlestoredb/mysql/tests/test_connection.py +851 -0
- singlestoredb/mysql/tests/test_converters.py +58 -0
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/mysql/tests/test_err.py +16 -0
- singlestoredb/mysql/tests/test_issues.py +514 -0
- singlestoredb/mysql/tests/test_load_local.py +75 -0
- singlestoredb/mysql/tests/test_nextset.py +88 -0
- singlestoredb/mysql/tests/test_optionfile.py +27 -0
- singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
- singlestoredb/mysql/times.py +23 -0
- singlestoredb/notebook/__init__.py +16 -0
- singlestoredb/notebook/_objects.py +213 -0
- singlestoredb/notebook/_portal.py +352 -0
- singlestoredb/py.typed +0 -0
- singlestoredb/pytest.py +352 -0
- singlestoredb/server/__init__.py +0 -0
- singlestoredb/server/docker.py +452 -0
- singlestoredb/server/free_tier.py +267 -0
- singlestoredb/tests/__init__.py +0 -0
- singlestoredb/tests/alltypes.sql +307 -0
- singlestoredb/tests/alltypes_no_nulls.sql +208 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +702 -0
- singlestoredb/tests/local_infile.csv +3 -0
- singlestoredb/tests/test.ipynb +18 -0
- singlestoredb/tests/test.sql +680 -0
- singlestoredb/tests/test2.ipynb +18 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +1332 -0
- singlestoredb/tests/test_config.py +318 -0
- singlestoredb/tests/test_connection.py +3103 -0
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +45 -0
- singlestoredb/tests/test_ext_func.py +1472 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +1527 -0
- singlestoredb/tests/test_http.py +288 -0
- singlestoredb/tests/test_management.py +1599 -0
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +171 -0
- singlestoredb/tests/test_types.py +132 -0
- singlestoredb/tests/test_udf.py +737 -0
- singlestoredb/tests/test_udf_returns.py +459 -0
- singlestoredb/tests/test_vectorstore.py +51 -0
- singlestoredb/tests/test_xdict.py +333 -0
- singlestoredb/tests/utils.py +141 -0
- singlestoredb/types.py +373 -0
- singlestoredb/utils/__init__.py +0 -0
- singlestoredb/utils/config.py +950 -0
- singlestoredb/utils/convert_rows.py +69 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/dtypes.py +205 -0
- singlestoredb/utils/events.py +65 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +585 -0
- singlestoredb/utils/xdict.py +425 -0
- singlestoredb/vectorstore.py +192 -0
- singlestoredb/warnings.py +5 -0
- singlestoredb-1.16.1.dist-info/METADATA +165 -0
- singlestoredb-1.16.1.dist-info/RECORD +183 -0
- singlestoredb-1.16.1.dist-info/WHEEL +5 -0
- singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
- singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
- singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
- sqlx/__init__.py +4 -0
- sqlx/magic.py +113 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""SingleStoreDB Base Manager."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Dict
|
|
8
|
+
from typing import List
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from typing import Union
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
import jwt
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
from .. import config
|
|
17
|
+
from ..exceptions import ManagementError
|
|
18
|
+
from ..exceptions import OperationalError
|
|
19
|
+
from .utils import get_token
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def set_organization(kwargs: Dict[str, Any]) -> None:
|
|
23
|
+
"""Set the organization ID in the dictionary."""
|
|
24
|
+
if kwargs.get('params', {}).get('organizationID', None):
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
|
|
28
|
+
if org:
|
|
29
|
+
if 'params' not in kwargs:
|
|
30
|
+
kwargs['params'] = {}
|
|
31
|
+
kwargs['params']['organizationID'] = org
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_jwt(token: str) -> bool:
|
|
35
|
+
"""Is the given token a JWT?"""
|
|
36
|
+
try:
|
|
37
|
+
jwt.decode(token, options={'verify_signature': False})
|
|
38
|
+
return True
|
|
39
|
+
except jwt.DecodeError:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Manager(object):
|
|
44
|
+
"""SingleStoreDB manager base class."""
|
|
45
|
+
|
|
46
|
+
#: Management API version if none is specified.
|
|
47
|
+
default_version = config.get_option('management.version') or 'v1'
|
|
48
|
+
|
|
49
|
+
#: Base URL if none is specified.
|
|
50
|
+
default_base_url = config.get_option('management.base_url') \
|
|
51
|
+
or 'https://api.singlestore.com'
|
|
52
|
+
|
|
53
|
+
#: Object type
|
|
54
|
+
obj_type = ''
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self, access_token: Optional[str] = None, version: Optional[str] = None,
|
|
58
|
+
base_url: Optional[str] = None, *, organization_id: Optional[str] = None,
|
|
59
|
+
):
|
|
60
|
+
from .. import __version__ as client_version
|
|
61
|
+
new_access_token = (
|
|
62
|
+
access_token or get_token()
|
|
63
|
+
)
|
|
64
|
+
if not new_access_token:
|
|
65
|
+
raise ManagementError(msg='No management token was configured.')
|
|
66
|
+
|
|
67
|
+
self._is_jwt = not access_token and new_access_token and is_jwt(new_access_token)
|
|
68
|
+
self._sess = requests.Session()
|
|
69
|
+
self._sess.headers.update({
|
|
70
|
+
'Authorization': f'Bearer {new_access_token}',
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
'Accept': 'application/json',
|
|
73
|
+
'User-Agent': f'SingleStoreDB-Python/{client_version}',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
self._base_url = urljoin(
|
|
77
|
+
base_url
|
|
78
|
+
or config.get_option('management.base_url')
|
|
79
|
+
or type(self).default_base_url,
|
|
80
|
+
version or type(self).default_version,
|
|
81
|
+
) + '/'
|
|
82
|
+
|
|
83
|
+
self._params: Dict[str, str] = {}
|
|
84
|
+
if organization_id:
|
|
85
|
+
self._params['organizationID'] = organization_id
|
|
86
|
+
|
|
87
|
+
def _check(
|
|
88
|
+
self, res: requests.Response, url: str, params: Dict[str, Any],
|
|
89
|
+
) -> requests.Response:
|
|
90
|
+
"""
|
|
91
|
+
Check the HTTP response status code and raise an exception as needed.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
res : requests.Response
|
|
96
|
+
HTTP response to check
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
requests.Response
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
if config.get_option('debug.queries'):
|
|
104
|
+
print(os.path.join(self._base_url, url), params, file=sys.stderr)
|
|
105
|
+
if res.status_code >= 400:
|
|
106
|
+
txt = res.text.strip()
|
|
107
|
+
msg = f'{txt}: /{url}'
|
|
108
|
+
if params:
|
|
109
|
+
new_params = params.copy()
|
|
110
|
+
if 'json' in new_params:
|
|
111
|
+
for k, v in new_params['json'].items():
|
|
112
|
+
if 'password' in k.lower() and v:
|
|
113
|
+
new_params['json'][k] = '*' * len(v)
|
|
114
|
+
msg += ': {}'.format(str(new_params))
|
|
115
|
+
raise ManagementError(errno=res.status_code, msg=msg, response=txt)
|
|
116
|
+
return res
|
|
117
|
+
|
|
118
|
+
def _doit(
|
|
119
|
+
self,
|
|
120
|
+
method: str,
|
|
121
|
+
path: str,
|
|
122
|
+
*args: Any,
|
|
123
|
+
**kwargs: Any,
|
|
124
|
+
) -> requests.Response:
|
|
125
|
+
"""Perform HTTP request."""
|
|
126
|
+
# Refresh the JWT as needed
|
|
127
|
+
if self._is_jwt:
|
|
128
|
+
self._sess.headers.update({'Authorization': f'Bearer {get_token()}'})
|
|
129
|
+
return getattr(self._sess, method.lower())(
|
|
130
|
+
urljoin(self._base_url, path), *args, **kwargs,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _get(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
134
|
+
"""
|
|
135
|
+
Invoke a GET request.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
path : str
|
|
140
|
+
Path of the resource
|
|
141
|
+
*args : positional arguments, optional
|
|
142
|
+
Arguments to add to the GET request
|
|
143
|
+
**kwargs : keyword arguments, optional
|
|
144
|
+
Keyword arguments to add to the GET request
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
requests.Response
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
if self._params:
|
|
152
|
+
params = dict(self._params)
|
|
153
|
+
params.update(kwargs.get('params', {}))
|
|
154
|
+
kwargs['params'] = params
|
|
155
|
+
set_organization(kwargs)
|
|
156
|
+
return self._check(self._doit('get', path, *args, **kwargs), path, kwargs)
|
|
157
|
+
|
|
158
|
+
def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
159
|
+
"""
|
|
160
|
+
Invoke a POST request.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
path : str
|
|
165
|
+
Path of the resource
|
|
166
|
+
*args : positional arguments, optional
|
|
167
|
+
Arguments to add to the POST request
|
|
168
|
+
**kwargs : keyword arguments, optional
|
|
169
|
+
Keyword arguments to add to the POST request
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
requests.Response
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
if self._params:
|
|
177
|
+
params = dict(self._params)
|
|
178
|
+
params.update(kwargs.get('params', {}))
|
|
179
|
+
kwargs['params'] = params
|
|
180
|
+
set_organization(kwargs)
|
|
181
|
+
return self._check(self._doit('post', path, *args, **kwargs), path, kwargs)
|
|
182
|
+
|
|
183
|
+
def _put(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
184
|
+
"""
|
|
185
|
+
Invoke a PUT request.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
path : str
|
|
190
|
+
Path of the resource
|
|
191
|
+
*args : positional arguments, optional
|
|
192
|
+
Arguments to add to the POST request
|
|
193
|
+
**kwargs : keyword arguments, optional
|
|
194
|
+
Keyword arguments to add to the POST request
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
requests.Response
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
if self._params:
|
|
202
|
+
params = dict(self._params)
|
|
203
|
+
params.update(kwargs.get('params', {}))
|
|
204
|
+
kwargs['params'] = params
|
|
205
|
+
set_organization(kwargs)
|
|
206
|
+
return self._check(self._doit('put', path, *args, **kwargs), path, kwargs)
|
|
207
|
+
|
|
208
|
+
def _delete(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
209
|
+
"""
|
|
210
|
+
Invoke a DELETE request.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
path : str
|
|
215
|
+
Path of the resource
|
|
216
|
+
*args : positional arguments, optional
|
|
217
|
+
Arguments to add to the DELETE request
|
|
218
|
+
**kwargs : keyword arguments, optional
|
|
219
|
+
Keyword arguments to add to the DELETE request
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
requests.Response
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
if self._params:
|
|
227
|
+
params = dict(self._params)
|
|
228
|
+
params.update(kwargs.get('params', {}))
|
|
229
|
+
kwargs['params'] = params
|
|
230
|
+
set_organization(kwargs)
|
|
231
|
+
return self._check(self._doit('delete', path, *args, **kwargs), path, kwargs)
|
|
232
|
+
|
|
233
|
+
def _patch(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
234
|
+
"""
|
|
235
|
+
Invoke a PATCH request.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
path : str
|
|
240
|
+
Path of the resource
|
|
241
|
+
*args : positional arguments, optional
|
|
242
|
+
Arguments to add to the PATCH request
|
|
243
|
+
**kwargs : keyword arguments, optional
|
|
244
|
+
Keyword arguments to add to the PATCH request
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
requests.Response
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
if self._params:
|
|
252
|
+
params = dict(self._params)
|
|
253
|
+
params.update(kwargs.get('params', {}))
|
|
254
|
+
kwargs['params'] = params
|
|
255
|
+
set_organization(kwargs)
|
|
256
|
+
return self._check(self._doit('patch', path, *args, **kwargs), path, kwargs)
|
|
257
|
+
|
|
258
|
+
def _wait_on_state(
|
|
259
|
+
self,
|
|
260
|
+
out: Any,
|
|
261
|
+
state: Union[str, List[str]],
|
|
262
|
+
interval: int = 20,
|
|
263
|
+
timeout: int = 600,
|
|
264
|
+
) -> Any:
|
|
265
|
+
"""
|
|
266
|
+
Wait on server state before continuing.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
out : Any
|
|
271
|
+
Current object
|
|
272
|
+
state : str or List[str]
|
|
273
|
+
State(s) to wait for
|
|
274
|
+
interval : int, optional
|
|
275
|
+
Interval between each server poll
|
|
276
|
+
timeout : int, optional
|
|
277
|
+
Maximum time to wait before raising an exception
|
|
278
|
+
|
|
279
|
+
Raises
|
|
280
|
+
------
|
|
281
|
+
ManagementError
|
|
282
|
+
If timeout is reached
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
Same object type as `out`
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
states = [
|
|
290
|
+
x.lower().strip()
|
|
291
|
+
for x in (isinstance(state, str) and [state] or state)
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
if getattr(out, 'state', None) is None:
|
|
295
|
+
raise ManagementError(
|
|
296
|
+
msg='{} object does not have a `state` attribute'.format(
|
|
297
|
+
type(out).__name__,
|
|
298
|
+
),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
while True:
|
|
302
|
+
if getattr(out, 'state').lower() in states:
|
|
303
|
+
break
|
|
304
|
+
if timeout <= 0:
|
|
305
|
+
raise ManagementError(
|
|
306
|
+
msg=f'Exceeded waiting time for {self.obj_type} to become '
|
|
307
|
+
'{}.'.format(', '.join(states)),
|
|
308
|
+
)
|
|
309
|
+
time.sleep(interval)
|
|
310
|
+
timeout -= interval
|
|
311
|
+
out = getattr(self, f'get_{self.obj_type}')(out.id)
|
|
312
|
+
|
|
313
|
+
return out
|
|
314
|
+
|
|
315
|
+
def _wait_on_endpoint(
|
|
316
|
+
self,
|
|
317
|
+
out: Any,
|
|
318
|
+
interval: int = 10,
|
|
319
|
+
timeout: int = 300,
|
|
320
|
+
) -> Any:
|
|
321
|
+
"""
|
|
322
|
+
Wait for the endpoint to be ready by attempting to connect.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
out : Any
|
|
327
|
+
Workspace object with a connect method
|
|
328
|
+
interval : int, optional
|
|
329
|
+
Interval between each connection attempt (default: 10 seconds)
|
|
330
|
+
timeout : int, optional
|
|
331
|
+
Maximum time to wait before raising an exception (default: 300 seconds)
|
|
332
|
+
|
|
333
|
+
Raises
|
|
334
|
+
------
|
|
335
|
+
ManagementError
|
|
336
|
+
If timeout is reached or endpoint is not available
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
Same object type as `out`
|
|
341
|
+
|
|
342
|
+
"""
|
|
343
|
+
# Only wait if workload type is set which means we are in the
|
|
344
|
+
# notebook environment. Outside of the environment, the endpoint
|
|
345
|
+
# may not be reachable directly.
|
|
346
|
+
if not os.environ.get('SINGLESTOREDB_WORKLOAD_TYPE', ''):
|
|
347
|
+
return out
|
|
348
|
+
|
|
349
|
+
if not hasattr(out, 'connect') or not out.connect:
|
|
350
|
+
raise ManagementError(
|
|
351
|
+
msg=f'{type(out).__name__} object does not have a valid endpoint',
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
try:
|
|
356
|
+
# Try to establish a connection to the endpoint using context manager
|
|
357
|
+
with out.connect(connect_timeout=5):
|
|
358
|
+
pass
|
|
359
|
+
except Exception as exc:
|
|
360
|
+
# If we get an 'access denied' error, that means that the server is
|
|
361
|
+
# up and we just aren't authenticating.
|
|
362
|
+
if isinstance(exc, OperationalError) and exc.errno == 1045:
|
|
363
|
+
break
|
|
364
|
+
# If connection fails, check timeout and retry
|
|
365
|
+
if timeout <= 0:
|
|
366
|
+
raise ManagementError(
|
|
367
|
+
msg=f'Exceeded waiting time for {self.obj_type} endpoint '
|
|
368
|
+
'to become ready',
|
|
369
|
+
)
|
|
370
|
+
time.sleep(interval)
|
|
371
|
+
timeout -= interval
|
|
372
|
+
|
|
373
|
+
return out
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""SingleStoreDB Cloud Organization."""
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
from ..exceptions import ManagementError
|
|
10
|
+
from .inference_api import InferenceAPIManager
|
|
11
|
+
from .job import JobsManager
|
|
12
|
+
from .manager import Manager
|
|
13
|
+
from .utils import vars_to_str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def listify(x: Union[str, List[str]]) -> List[str]:
|
|
17
|
+
if isinstance(x, list):
|
|
18
|
+
return x
|
|
19
|
+
return [x]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def stringify(x: Union[str, List[str]]) -> str:
|
|
23
|
+
if isinstance(x, list):
|
|
24
|
+
return x[0]
|
|
25
|
+
return x
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Secret(object):
|
|
29
|
+
"""
|
|
30
|
+
SingleStoreDB secrets definition.
|
|
31
|
+
|
|
32
|
+
This object is not directly instantiated. It is used in results
|
|
33
|
+
of API calls on the :class:`Organization`. See :meth:`Organization.get_secret`.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
id: str,
|
|
39
|
+
name: str,
|
|
40
|
+
created_by: str,
|
|
41
|
+
created_at: Union[str, datetime.datetime],
|
|
42
|
+
last_updated_by: str,
|
|
43
|
+
last_updated_at: Union[str, datetime.datetime],
|
|
44
|
+
value: Optional[str] = None,
|
|
45
|
+
deleted_by: Optional[str] = None,
|
|
46
|
+
deleted_at: Optional[Union[str, datetime.datetime]] = None,
|
|
47
|
+
):
|
|
48
|
+
# UUID of the secret
|
|
49
|
+
self.id = id
|
|
50
|
+
|
|
51
|
+
# Name of the secret
|
|
52
|
+
self.name = name
|
|
53
|
+
|
|
54
|
+
# Value of the secret
|
|
55
|
+
self.value = value
|
|
56
|
+
|
|
57
|
+
# User who created the secret
|
|
58
|
+
self.created_by = created_by
|
|
59
|
+
|
|
60
|
+
# Time when the secret was created
|
|
61
|
+
self.created_at = created_at
|
|
62
|
+
|
|
63
|
+
# UUID of the user who last updated the secret
|
|
64
|
+
self.last_updated_by = last_updated_by
|
|
65
|
+
|
|
66
|
+
# Time when the secret was last updated
|
|
67
|
+
self.last_updated_at = last_updated_at
|
|
68
|
+
|
|
69
|
+
# UUID of the user who deleted the secret
|
|
70
|
+
self.deleted_by = deleted_by
|
|
71
|
+
|
|
72
|
+
# Time when the secret was deleted
|
|
73
|
+
self.deleted_at = deleted_at
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, obj: Dict[str, str]) -> 'Secret':
|
|
77
|
+
"""
|
|
78
|
+
Construct a Secret from a dictionary of values.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
obj : dict
|
|
83
|
+
Dictionary of values
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
:class:`Secret`
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
out = cls(
|
|
91
|
+
id=obj['secretID'],
|
|
92
|
+
name=obj['name'],
|
|
93
|
+
created_by=obj['createdBy'],
|
|
94
|
+
created_at=obj['createdAt'],
|
|
95
|
+
last_updated_by=obj['lastUpdatedBy'],
|
|
96
|
+
last_updated_at=obj['lastUpdatedAt'],
|
|
97
|
+
value=obj.get('value'),
|
|
98
|
+
deleted_by=obj.get('deletedBy'),
|
|
99
|
+
deleted_at=obj.get('deletedAt'),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return out
|
|
103
|
+
|
|
104
|
+
def __str__(self) -> str:
|
|
105
|
+
"""Return string representation."""
|
|
106
|
+
return vars_to_str(self)
|
|
107
|
+
|
|
108
|
+
def __repr__(self) -> str:
|
|
109
|
+
"""Return string representation."""
|
|
110
|
+
return str(self)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Organization(object):
|
|
114
|
+
"""
|
|
115
|
+
Organization in SingleStoreDB Cloud portal.
|
|
116
|
+
|
|
117
|
+
This object is not directly instantiated. It is used in results
|
|
118
|
+
of ``WorkspaceManager`` API calls.
|
|
119
|
+
|
|
120
|
+
See Also
|
|
121
|
+
--------
|
|
122
|
+
:attr:`WorkspaceManager.organization`
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
id: str
|
|
127
|
+
name: str
|
|
128
|
+
firewall_ranges: List[str]
|
|
129
|
+
|
|
130
|
+
def __init__(self, id: str, name: str, firewall_ranges: List[str]):
|
|
131
|
+
"""Use :attr:`WorkspaceManager.organization` instead."""
|
|
132
|
+
#: Unique ID of the organization
|
|
133
|
+
self.id = id
|
|
134
|
+
|
|
135
|
+
#: Name of the organization
|
|
136
|
+
self.name = name
|
|
137
|
+
|
|
138
|
+
#: Firewall ranges of the organization
|
|
139
|
+
self.firewall_ranges = list(firewall_ranges)
|
|
140
|
+
|
|
141
|
+
self._manager: Optional[Manager] = None
|
|
142
|
+
|
|
143
|
+
def __str__(self) -> str:
|
|
144
|
+
"""Return string representation."""
|
|
145
|
+
return vars_to_str(self)
|
|
146
|
+
|
|
147
|
+
def __repr__(self) -> str:
|
|
148
|
+
"""Return string representation."""
|
|
149
|
+
return str(self)
|
|
150
|
+
|
|
151
|
+
def get_secret(self, name: str) -> Secret:
|
|
152
|
+
if self._manager is None:
|
|
153
|
+
raise ManagementError(msg='Organization not initialized')
|
|
154
|
+
|
|
155
|
+
res = self._manager._get('secrets', params=dict(name=name))
|
|
156
|
+
|
|
157
|
+
secrets = [Secret.from_dict(item) for item in res.json()['secrets']]
|
|
158
|
+
|
|
159
|
+
if len(secrets) == 0:
|
|
160
|
+
raise ManagementError(msg=f'Secret {name} not found')
|
|
161
|
+
|
|
162
|
+
if len(secrets) > 1:
|
|
163
|
+
raise ManagementError(msg=f'Multiple secrets found for {name}')
|
|
164
|
+
|
|
165
|
+
return secrets[0]
|
|
166
|
+
|
|
167
|
+
@classmethod
|
|
168
|
+
def from_dict(
|
|
169
|
+
cls,
|
|
170
|
+
obj: Dict[str, Union[str, List[str]]],
|
|
171
|
+
manager: Manager,
|
|
172
|
+
) -> 'Organization':
|
|
173
|
+
"""
|
|
174
|
+
Convert dictionary to an ``Organization`` object.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
obj : dict
|
|
179
|
+
Key-value pairs to retrieve organization information from
|
|
180
|
+
manager : WorkspaceManager, optional
|
|
181
|
+
The WorkspaceManager the Organization belongs to
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
:class:`Organization`
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
out = cls(
|
|
189
|
+
id=stringify(obj['orgID']),
|
|
190
|
+
name=stringify(obj.get('name', '<unknown>')),
|
|
191
|
+
firewall_ranges=listify(obj.get('firewallRanges', [])),
|
|
192
|
+
)
|
|
193
|
+
out._manager = manager
|
|
194
|
+
return out
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def jobs(self) -> JobsManager:
|
|
198
|
+
"""
|
|
199
|
+
Retrieve a SingleStoreDB scheduled job manager.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
manager : WorkspaceManager, optional
|
|
204
|
+
The WorkspaceManager the JobsManager belongs to
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
:class:`JobsManager`
|
|
209
|
+
"""
|
|
210
|
+
return JobsManager(self._manager)
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def inference_apis(self) -> InferenceAPIManager:
|
|
214
|
+
"""
|
|
215
|
+
Retrieve a SingleStoreDB inference api manager.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
manager : WorkspaceManager, optional
|
|
220
|
+
The WorkspaceManager the InferenceAPIManager belongs to
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
:class:`InferenceAPIManager`
|
|
225
|
+
"""
|
|
226
|
+
return InferenceAPIManager(self._manager)
|