singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__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.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +33 -1
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +5 -1
- singlestoredb/config.py +116 -14
- singlestoredb/connection.py +483 -516
- singlestoredb/converters.py +238 -135
- singlestoredb/exceptions.py +30 -2
- singlestoredb/functions/__init__.py +1 -0
- singlestoredb/functions/decorator.py +142 -0
- singlestoredb/functions/dtypes.py +1639 -0
- singlestoredb/functions/ext/__init__.py +2 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +661 -0
- singlestoredb/functions/ext/json.py +427 -0
- singlestoredb/functions/ext/mmap.py +306 -0
- singlestoredb/functions/ext/rowdat_1.py +744 -0
- singlestoredb/functions/signature.py +673 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +621 -0
- singlestoredb/fusion/handlers/stage.py +257 -0
- singlestoredb/fusion/handlers/utils.py +162 -0
- singlestoredb/fusion/handlers/workspace.py +412 -0
- singlestoredb/fusion/registry.py +164 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/{http.py → http/connection.py} +555 -154
- singlestoredb/management/__init__.py +3 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +14 -6
- singlestoredb/management/manager.py +100 -38
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +5 -5
- singlestoredb/management/utils.py +281 -2
- singlestoredb/management/workspace.py +1344 -49
- singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
- singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
- singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
- singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
- singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
- singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
- singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
- singlestoredb/pytest.py +283 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +385 -0
- singlestoredb/tests/test.sql +210 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +482 -115
- singlestoredb/tests/test_config.py +13 -13
- singlestoredb/tests/test_connection.py +241 -305
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_ext_func.py +1193 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +465 -0
- singlestoredb/tests/test_http.py +32 -26
- singlestoredb/tests/test_management.py +588 -8
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -12
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/utils.py +3 -2
- singlestoredb/utils/config.py +58 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -1
- singlestoredb-1.0.4.dist-info/METADATA +139 -0
- singlestoredb-1.0.4.dist-info/RECORD +112 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
- singlestoredb/clients/pymysqlsv/converters.py +0 -365
- singlestoredb/clients/pymysqlsv/err.py +0 -144
- singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
- singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
- singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
- singlestoredb/drivers/__init__.py +0 -45
- singlestoredb/drivers/base.py +0 -198
- singlestoredb/drivers/cymysql.py +0 -38
- singlestoredb/drivers/http.py +0 -47
- singlestoredb/drivers/mariadb.py +0 -40
- singlestoredb/drivers/mysqlconnector.py +0 -49
- singlestoredb/drivers/mysqldb.py +0 -60
- singlestoredb/drivers/pymysql.py +0 -37
- singlestoredb/drivers/pymysqlsv.py +0 -35
- singlestoredb/drivers/pyodbc.py +0 -65
- singlestoredb-0.4.0.dist-info/METADATA +0 -111
- singlestoredb-0.4.0.dist-info/RECORD +0 -86
- /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
- /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""SingleStoreDB Cloud Billing Usage."""
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
from typing import Dict
|
|
6
|
+
from typing import List
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from .manager import Manager
|
|
10
|
+
from .utils import camel_to_snake
|
|
11
|
+
from .utils import vars_to_str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UsageItem(object):
|
|
15
|
+
"""Usage statistics."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
start_time: datetime.datetime,
|
|
20
|
+
end_time: datetime.datetime,
|
|
21
|
+
owner_id: str,
|
|
22
|
+
resource_id: str,
|
|
23
|
+
resource_name: str,
|
|
24
|
+
resource_type: str,
|
|
25
|
+
value: str,
|
|
26
|
+
):
|
|
27
|
+
#: Starting time for the usage duration
|
|
28
|
+
self.start_time = start_time
|
|
29
|
+
|
|
30
|
+
#: Ending time for the usage duration
|
|
31
|
+
self.end_time = end_time
|
|
32
|
+
|
|
33
|
+
#: Owner ID
|
|
34
|
+
self.owner_id = owner_id
|
|
35
|
+
|
|
36
|
+
#: Resource ID
|
|
37
|
+
self.resource_id = resource_id
|
|
38
|
+
|
|
39
|
+
#: Resource name
|
|
40
|
+
self.resource_name = resource_name
|
|
41
|
+
|
|
42
|
+
#: Resource type
|
|
43
|
+
self.resource_type = resource_type
|
|
44
|
+
|
|
45
|
+
#: Usage statistic value
|
|
46
|
+
self.value = value
|
|
47
|
+
|
|
48
|
+
self._manager: Optional[Manager] = None
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
"""Return string representation."""
|
|
52
|
+
return vars_to_str(self)
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
"""Return string representation."""
|
|
56
|
+
return str(self)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def from_dict(
|
|
60
|
+
cls,
|
|
61
|
+
obj: Dict[str, Any],
|
|
62
|
+
manager: Manager,
|
|
63
|
+
) -> 'UsageItem':
|
|
64
|
+
"""
|
|
65
|
+
Convert dictionary to a ``UsageItem`` object.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
obj : dict
|
|
70
|
+
Key-value pairs to retrieve billling usage information from
|
|
71
|
+
manager : WorkspaceManager, optional
|
|
72
|
+
The WorkspaceManager the UsageItem belongs to
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
:class:`UsageItem`
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
out = cls(
|
|
80
|
+
end_time=datetime.datetime.fromisoformat(obj['endTime']),
|
|
81
|
+
start_time=datetime.datetime.fromisoformat(obj['startTime']),
|
|
82
|
+
owner_id=obj['ownerId'],
|
|
83
|
+
resource_id=obj['resourceId'],
|
|
84
|
+
resource_name=obj['resourceName'],
|
|
85
|
+
resource_type=obj['resource_type'],
|
|
86
|
+
value=obj['value'],
|
|
87
|
+
)
|
|
88
|
+
out._manager = manager
|
|
89
|
+
return out
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class BillingUsageItem(object):
|
|
93
|
+
"""Billing usage item."""
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
description: str,
|
|
98
|
+
metric: str,
|
|
99
|
+
usage: List[UsageItem],
|
|
100
|
+
):
|
|
101
|
+
"""Use :attr:`WorkspaceManager.billing.usage` instead."""
|
|
102
|
+
#: Description of the usage metric
|
|
103
|
+
self.description = description
|
|
104
|
+
|
|
105
|
+
#: Name of the usage metric
|
|
106
|
+
self.metric = metric
|
|
107
|
+
|
|
108
|
+
#: Usage statistics
|
|
109
|
+
self.usage = list(usage)
|
|
110
|
+
|
|
111
|
+
self._manager: Optional[Manager] = None
|
|
112
|
+
|
|
113
|
+
def __str__(self) -> str:
|
|
114
|
+
"""Return string representation."""
|
|
115
|
+
return vars_to_str(self)
|
|
116
|
+
|
|
117
|
+
def __repr__(self) -> str:
|
|
118
|
+
"""Return string representation."""
|
|
119
|
+
return str(self)
|
|
120
|
+
|
|
121
|
+
@ classmethod
|
|
122
|
+
def from_dict(
|
|
123
|
+
cls,
|
|
124
|
+
obj: Dict[str, Any],
|
|
125
|
+
manager: Manager,
|
|
126
|
+
) -> 'BillingUsageItem':
|
|
127
|
+
"""
|
|
128
|
+
Convert dictionary to a ``BillingUsageItem`` object.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
obj : dict
|
|
133
|
+
Key-value pairs to retrieve billling usage information from
|
|
134
|
+
manager : WorkspaceManager, optional
|
|
135
|
+
The WorkspaceManager the BillingUsageItem belongs to
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
:class:`BillingUsageItem`
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
out = cls(
|
|
143
|
+
description=obj['description'],
|
|
144
|
+
metric=str(camel_to_snake(obj['metric'])),
|
|
145
|
+
usage=[UsageItem.from_dict(x, manager) for x in obj['Usage']],
|
|
146
|
+
)
|
|
147
|
+
out._manager = manager
|
|
148
|
+
return out
|
|
@@ -12,6 +12,7 @@ from .. import connection
|
|
|
12
12
|
from ..exceptions import ManagementError
|
|
13
13
|
from .manager import Manager
|
|
14
14
|
from .region import Region
|
|
15
|
+
from .utils import NamedList
|
|
15
16
|
from .utils import to_datetime
|
|
16
17
|
from .utils import vars_to_str
|
|
17
18
|
|
|
@@ -337,16 +338,16 @@ class ClusterManager(Manager):
|
|
|
337
338
|
obj_type = 'cluster'
|
|
338
339
|
|
|
339
340
|
@property
|
|
340
|
-
def clusters(self) ->
|
|
341
|
+
def clusters(self) -> NamedList[Cluster]:
|
|
341
342
|
"""Return a list of available clusters."""
|
|
342
343
|
res = self._get('clusters')
|
|
343
|
-
return [Cluster.from_dict(item, self) for item in res.json()]
|
|
344
|
+
return NamedList([Cluster.from_dict(item, self) for item in res.json()])
|
|
344
345
|
|
|
345
346
|
@property
|
|
346
|
-
def regions(self) ->
|
|
347
|
+
def regions(self) -> NamedList[Region]:
|
|
347
348
|
"""Return a list of available regions."""
|
|
348
349
|
res = self._get('regions')
|
|
349
|
-
return [Region.from_dict(item, self) for item in res.json()]
|
|
350
|
+
return NamedList([Region.from_dict(item, self) for item in res.json()])
|
|
350
351
|
|
|
351
352
|
def create_cluster(
|
|
352
353
|
self, name: str, region: Union[str, Region], admin_password: str,
|
|
@@ -426,6 +427,8 @@ def manage_cluster(
|
|
|
426
427
|
access_token: Optional[str] = None,
|
|
427
428
|
version: str = ClusterManager.default_version,
|
|
428
429
|
base_url: str = ClusterManager.default_base_url,
|
|
430
|
+
*,
|
|
431
|
+
organization_id: Optional[str] = None,
|
|
429
432
|
) -> ClusterManager:
|
|
430
433
|
"""
|
|
431
434
|
Retrieve a SingleStoreDB cluster manager.
|
|
@@ -438,6 +441,8 @@ def manage_cluster(
|
|
|
438
441
|
Version of the API to use
|
|
439
442
|
base_url : str, optional
|
|
440
443
|
Base URL of the cluster management API
|
|
444
|
+
organization_id: str, optional
|
|
445
|
+
ID of organization, if using a JWT for authentication
|
|
441
446
|
|
|
442
447
|
Returns
|
|
443
448
|
-------
|
|
@@ -446,7 +451,10 @@ def manage_cluster(
|
|
|
446
451
|
"""
|
|
447
452
|
warnings.warn(
|
|
448
453
|
'The cluster management API is deprecated; '
|
|
449
|
-
'use
|
|
454
|
+
'use manage_workspaces instead.',
|
|
450
455
|
category=DeprecationWarning,
|
|
451
456
|
)
|
|
452
|
-
return ClusterManager(
|
|
457
|
+
return ClusterManager(
|
|
458
|
+
access_token=access_token, base_url=base_url,
|
|
459
|
+
version=version, organization_id=organization_id,
|
|
460
|
+
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB Base Manager."""
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
3
5
|
import time
|
|
4
6
|
from typing import Any
|
|
5
7
|
from typing import Dict
|
|
@@ -8,44 +10,73 @@ from typing import Optional
|
|
|
8
10
|
from typing import Union
|
|
9
11
|
from urllib.parse import urljoin
|
|
10
12
|
|
|
13
|
+
import jwt
|
|
11
14
|
import requests
|
|
12
15
|
|
|
13
16
|
from .. import config
|
|
14
17
|
from ..exceptions import ManagementError
|
|
18
|
+
from .utils import get_organization
|
|
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 = get_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
|
|
15
41
|
|
|
16
42
|
|
|
17
43
|
class Manager(object):
|
|
18
44
|
"""SingleStoreDB manager base class."""
|
|
19
45
|
|
|
20
46
|
#: Management API version if none is specified.
|
|
21
|
-
default_version = '
|
|
47
|
+
default_version = config.get_option('management.version')
|
|
22
48
|
|
|
23
49
|
#: Base URL if none is specified.
|
|
24
|
-
default_base_url = '
|
|
50
|
+
default_base_url = config.get_option('management.base_url')
|
|
25
51
|
|
|
26
52
|
#: Object type
|
|
27
53
|
obj_type = ''
|
|
28
54
|
|
|
29
55
|
def __init__(
|
|
30
56
|
self, access_token: Optional[str] = None, version: Optional[str] = None,
|
|
31
|
-
base_url: Optional[str] = None,
|
|
57
|
+
base_url: Optional[str] = None, *, organization_id: Optional[str] = None,
|
|
32
58
|
):
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
from .. import __version__ as client_version
|
|
60
|
+
new_access_token = (
|
|
61
|
+
access_token or get_token()
|
|
36
62
|
)
|
|
37
|
-
if not
|
|
63
|
+
if not new_access_token:
|
|
38
64
|
raise ManagementError(msg='No management token was configured.')
|
|
65
|
+
self._is_jwt = not access_token and new_access_token and is_jwt(new_access_token)
|
|
39
66
|
self._sess = requests.Session()
|
|
40
67
|
self._sess.headers.update({
|
|
41
|
-
'Authorization': f'Bearer {
|
|
68
|
+
'Authorization': f'Bearer {new_access_token}',
|
|
42
69
|
'Content-Type': 'application/json',
|
|
43
70
|
'Accept': 'application/json',
|
|
71
|
+
'User-Agent': f'SingleStoreDB-Python/{client_version}',
|
|
44
72
|
})
|
|
45
73
|
self._base_url = urljoin(
|
|
46
74
|
base_url or type(self).default_base_url,
|
|
47
75
|
version or type(self).default_version,
|
|
48
76
|
) + '/'
|
|
77
|
+
self._params: Dict[str, str] = {}
|
|
78
|
+
if organization_id:
|
|
79
|
+
self._params['organizationID'] = organization_id
|
|
49
80
|
|
|
50
81
|
def _check(
|
|
51
82
|
self, res: requests.Response, url: str, params: Dict[str, Any],
|
|
@@ -63,6 +94,8 @@ class Manager(object):
|
|
|
63
94
|
requests.Response
|
|
64
95
|
|
|
65
96
|
"""
|
|
97
|
+
if config.get_option('debug.queries'):
|
|
98
|
+
print(os.path.join(self._base_url, url), params, file=sys.stderr)
|
|
66
99
|
if res.status_code >= 400:
|
|
67
100
|
txt = res.text.strip()
|
|
68
101
|
msg = f'{txt}: /{url}'
|
|
@@ -70,12 +103,27 @@ class Manager(object):
|
|
|
70
103
|
new_params = params.copy()
|
|
71
104
|
if 'json' in new_params:
|
|
72
105
|
for k, v in new_params['json'].items():
|
|
73
|
-
if 'password' in k.lower():
|
|
106
|
+
if 'password' in k.lower() and v:
|
|
74
107
|
new_params['json'][k] = '*' * len(v)
|
|
75
108
|
msg += ': {}'.format(str(new_params))
|
|
76
|
-
raise ManagementError(errno=res.status_code, msg=msg)
|
|
109
|
+
raise ManagementError(errno=res.status_code, msg=msg, response=txt)
|
|
77
110
|
return res
|
|
78
111
|
|
|
112
|
+
def _doit(
|
|
113
|
+
self,
|
|
114
|
+
method: str,
|
|
115
|
+
path: str,
|
|
116
|
+
*args: Any,
|
|
117
|
+
**kwargs: Any,
|
|
118
|
+
) -> requests.Response:
|
|
119
|
+
"""Perform HTTP request."""
|
|
120
|
+
# Refresh the JWT as needed
|
|
121
|
+
if self._is_jwt:
|
|
122
|
+
self._sess.headers.update({'Authorization': f'Bearer {get_token()}'})
|
|
123
|
+
return getattr(self._sess, method.lower())(
|
|
124
|
+
urljoin(self._base_url, path), *args, **kwargs,
|
|
125
|
+
)
|
|
126
|
+
|
|
79
127
|
def _get(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
80
128
|
"""
|
|
81
129
|
Invoke a GET request.
|
|
@@ -94,13 +142,10 @@ class Manager(object):
|
|
|
94
142
|
requests.Response
|
|
95
143
|
|
|
96
144
|
"""
|
|
97
|
-
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
),
|
|
102
|
-
path, kwargs,
|
|
103
|
-
)
|
|
145
|
+
if self._params:
|
|
146
|
+
kwargs['params'] = self._params
|
|
147
|
+
set_organization(kwargs)
|
|
148
|
+
return self._check(self._doit('get', path, *args, **kwargs), path, kwargs)
|
|
104
149
|
|
|
105
150
|
def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
106
151
|
"""
|
|
@@ -120,13 +165,33 @@ class Manager(object):
|
|
|
120
165
|
requests.Response
|
|
121
166
|
|
|
122
167
|
"""
|
|
123
|
-
|
|
124
|
-
self.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
168
|
+
if self._params:
|
|
169
|
+
kwargs['params'] = self._params
|
|
170
|
+
set_organization(kwargs)
|
|
171
|
+
return self._check(self._doit('post', path, *args, **kwargs), path, kwargs)
|
|
172
|
+
|
|
173
|
+
def _put(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
174
|
+
"""
|
|
175
|
+
Invoke a PUT request.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
path : str
|
|
180
|
+
Path of the resource
|
|
181
|
+
*args : positional arguments, optional
|
|
182
|
+
Arguments to add to the POST request
|
|
183
|
+
**kwargs : keyword arguments, optional
|
|
184
|
+
Keyword arguments to add to the POST request
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
requests.Response
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
if self._params:
|
|
192
|
+
kwargs['params'] = self._params
|
|
193
|
+
set_organization(kwargs)
|
|
194
|
+
return self._check(self._doit('put', path, *args, **kwargs), path, kwargs)
|
|
130
195
|
|
|
131
196
|
def _delete(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
132
197
|
"""
|
|
@@ -146,13 +211,10 @@ class Manager(object):
|
|
|
146
211
|
requests.Response
|
|
147
212
|
|
|
148
213
|
"""
|
|
149
|
-
|
|
150
|
-
self.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
),
|
|
154
|
-
path, kwargs,
|
|
155
|
-
)
|
|
214
|
+
if self._params:
|
|
215
|
+
kwargs['params'] = self._params
|
|
216
|
+
set_organization(kwargs)
|
|
217
|
+
return self._check(self._doit('delete', path, *args, **kwargs), path, kwargs)
|
|
156
218
|
|
|
157
219
|
def _patch(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
158
220
|
"""
|
|
@@ -172,13 +234,10 @@ class Manager(object):
|
|
|
172
234
|
requests.Response
|
|
173
235
|
|
|
174
236
|
"""
|
|
175
|
-
|
|
176
|
-
self.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
),
|
|
180
|
-
path, kwargs,
|
|
181
|
-
)
|
|
237
|
+
if self._params:
|
|
238
|
+
kwargs['params'] = self._params
|
|
239
|
+
set_organization(kwargs)
|
|
240
|
+
return self._check(self._doit('patch', path, *args, **kwargs), path, kwargs)
|
|
182
241
|
|
|
183
242
|
def _wait_on_state(
|
|
184
243
|
self,
|
|
@@ -215,12 +274,14 @@ class Manager(object):
|
|
|
215
274
|
x.lower().strip()
|
|
216
275
|
for x in (isinstance(state, str) and [state] or state)
|
|
217
276
|
]
|
|
277
|
+
|
|
218
278
|
if getattr(out, 'state', None) is None:
|
|
219
279
|
raise ManagementError(
|
|
220
280
|
msg='{} object does not have a `state` attribute'.format(
|
|
221
281
|
type(out).__name__,
|
|
222
282
|
),
|
|
223
283
|
)
|
|
284
|
+
|
|
224
285
|
while True:
|
|
225
286
|
if getattr(out, 'state').lower() in states:
|
|
226
287
|
break
|
|
@@ -232,4 +293,5 @@ class Manager(object):
|
|
|
232
293
|
time.sleep(interval)
|
|
233
294
|
timeout -= interval
|
|
234
295
|
out = getattr(self, f'get_{self.obj_type}')(out.id)
|
|
296
|
+
|
|
235
297
|
return out
|
|
@@ -0,0 +1,188 @@
|
|
|
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 .manager import Manager
|
|
11
|
+
from .utils import vars_to_str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def listify(x: Union[str, List[str]]) -> List[str]:
|
|
15
|
+
if isinstance(x, list):
|
|
16
|
+
return x
|
|
17
|
+
return [x]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def stringify(x: Union[str, List[str]]) -> str:
|
|
21
|
+
if isinstance(x, list):
|
|
22
|
+
return x[0]
|
|
23
|
+
return x
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Secret(object):
|
|
27
|
+
"""
|
|
28
|
+
SingleStoreDB secrets definition.
|
|
29
|
+
|
|
30
|
+
This object is not directly instantiated. It is used in results
|
|
31
|
+
of API calls on the :class:`Organization`. See :meth:`Organization.get_secret`.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
id: str,
|
|
37
|
+
name: str,
|
|
38
|
+
value: str,
|
|
39
|
+
created_by: str,
|
|
40
|
+
created_at: Union[str, datetime.datetime],
|
|
41
|
+
last_updated_by: str,
|
|
42
|
+
last_updated_at: Union[str, datetime.datetime],
|
|
43
|
+
deleted_by: Optional[str] = None,
|
|
44
|
+
deleted_at: Optional[Union[str, datetime.datetime]] = None,
|
|
45
|
+
):
|
|
46
|
+
# UUID of the secret
|
|
47
|
+
self.id = id
|
|
48
|
+
|
|
49
|
+
# Name of the secret
|
|
50
|
+
self.name = name
|
|
51
|
+
|
|
52
|
+
# Value of the secret
|
|
53
|
+
self.value = value
|
|
54
|
+
|
|
55
|
+
# User who created the secret
|
|
56
|
+
self.created_by = created_by
|
|
57
|
+
|
|
58
|
+
# Time when the secret was created
|
|
59
|
+
self.created_at = created_at
|
|
60
|
+
|
|
61
|
+
# UUID of the user who last updated the secret
|
|
62
|
+
self.last_updated_by = last_updated_by
|
|
63
|
+
|
|
64
|
+
# Time when the secret was last updated
|
|
65
|
+
self.last_updated_at = last_updated_at
|
|
66
|
+
|
|
67
|
+
# UUID of the user who deleted the secret
|
|
68
|
+
self.deleted_by = deleted_by
|
|
69
|
+
|
|
70
|
+
# Time when the secret was deleted
|
|
71
|
+
self.deleted_at = deleted_at
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(cls, obj: Dict[str, str]) -> 'Secret':
|
|
75
|
+
"""
|
|
76
|
+
Construct a Secret from a dictionary of values.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
obj : dict
|
|
81
|
+
Dictionary of values
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
:class:`Secret`
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
out = cls(
|
|
89
|
+
id=obj['secretID'],
|
|
90
|
+
name=obj['name'],
|
|
91
|
+
value=obj['value'],
|
|
92
|
+
created_by=obj['createdBy'],
|
|
93
|
+
created_at=obj['createdAt'],
|
|
94
|
+
last_updated_by=obj['lastUpdatedBy'],
|
|
95
|
+
last_updated_at=obj['lastUpdatedAt'],
|
|
96
|
+
deleted_by=obj.get('deletedBy'),
|
|
97
|
+
deleted_at=obj.get('deletedAt'),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
def __str__(self) -> str:
|
|
103
|
+
"""Return string representation."""
|
|
104
|
+
return vars_to_str(self)
|
|
105
|
+
|
|
106
|
+
def __repr__(self) -> str:
|
|
107
|
+
"""Return string representation."""
|
|
108
|
+
return str(self)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Organization(object):
|
|
112
|
+
"""
|
|
113
|
+
Organization in SingleStoreDB Cloud portal.
|
|
114
|
+
|
|
115
|
+
This object is not directly instantiated. It is used in results
|
|
116
|
+
of ``WorkspaceManager`` API calls.
|
|
117
|
+
|
|
118
|
+
See Also
|
|
119
|
+
--------
|
|
120
|
+
:attr:`WorkspaceManager.organization`
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, id: str, name: str, firewall_ranges: List[str]):
|
|
125
|
+
"""Use :attr:`WorkspaceManager.organization` instead."""
|
|
126
|
+
#: Unique ID of the organization
|
|
127
|
+
self.id = id
|
|
128
|
+
|
|
129
|
+
#: Name of the organization
|
|
130
|
+
self.name = name
|
|
131
|
+
|
|
132
|
+
#: Firewall ranges of the organization
|
|
133
|
+
self.firewall_ranges = list(firewall_ranges)
|
|
134
|
+
|
|
135
|
+
self._manager: Optional[Manager] = None
|
|
136
|
+
|
|
137
|
+
def __str__(self) -> str:
|
|
138
|
+
"""Return string representation."""
|
|
139
|
+
return vars_to_str(self)
|
|
140
|
+
|
|
141
|
+
def __repr__(self) -> str:
|
|
142
|
+
"""Return string representation."""
|
|
143
|
+
return str(self)
|
|
144
|
+
|
|
145
|
+
def get_secret(self, name: str) -> Secret:
|
|
146
|
+
if self._manager is None:
|
|
147
|
+
raise ManagementError(msg='Organization not initialized')
|
|
148
|
+
|
|
149
|
+
res = self._manager._get('secrets', params=dict(name=name))
|
|
150
|
+
|
|
151
|
+
secrets = [Secret.from_dict(item) for item in res.json()['secrets']]
|
|
152
|
+
|
|
153
|
+
if len(secrets) == 0:
|
|
154
|
+
raise ManagementError(msg=f'Secret {name} not found')
|
|
155
|
+
|
|
156
|
+
if len(secrets) > 1:
|
|
157
|
+
raise ManagementError(msg=f'Multiple secrets found for {name}')
|
|
158
|
+
|
|
159
|
+
return secrets[0]
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def from_dict(
|
|
163
|
+
cls,
|
|
164
|
+
obj: Dict[str, Union[str, List[str]]],
|
|
165
|
+
manager: Manager,
|
|
166
|
+
) -> 'Organization':
|
|
167
|
+
"""
|
|
168
|
+
Convert dictionary to an ``Organization`` object.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
obj : dict
|
|
173
|
+
Key-value pairs to retrieve organization information from
|
|
174
|
+
manager : WorkspaceManager, optional
|
|
175
|
+
The WorkspaceManager the Organization belongs to
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
:class:`Organization`
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
out = cls(
|
|
183
|
+
id=stringify(obj['orgID']),
|
|
184
|
+
name=stringify(obj.get('name', '<unknown>')),
|
|
185
|
+
firewall_ranges=listify(obj.get('firewallRanges', [])),
|
|
186
|
+
)
|
|
187
|
+
out._manager = manager
|
|
188
|
+
return out
|
|
@@ -12,16 +12,16 @@ class Region(object):
|
|
|
12
12
|
Cluster region information.
|
|
13
13
|
|
|
14
14
|
This object is not directly instantiated. It is used in results
|
|
15
|
-
of
|
|
15
|
+
of ``WorkspaceManager`` API calls.
|
|
16
16
|
|
|
17
17
|
See Also
|
|
18
18
|
--------
|
|
19
|
-
:attr:`
|
|
19
|
+
:attr:`WorkspaceManager.regions`
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
def __init__(self, id: str, name: str, provider: str):
|
|
24
|
-
"""Use :attr:`
|
|
24
|
+
"""Use :attr:`WorkspaceManager.regions` instead."""
|
|
25
25
|
#: Unique ID of the region
|
|
26
26
|
self.id = id
|
|
27
27
|
|
|
@@ -50,8 +50,8 @@ class Region(object):
|
|
|
50
50
|
----------
|
|
51
51
|
obj : dict
|
|
52
52
|
Key-value pairs to retrieve region information from
|
|
53
|
-
manager :
|
|
54
|
-
The
|
|
53
|
+
manager : WorkspaceManager, optional
|
|
54
|
+
The WorkspaceManager the Region belongs to
|
|
55
55
|
|
|
56
56
|
Returns
|
|
57
57
|
-------
|