singlestoredb 0.3.3__py3-none-any.whl → 1.0.3__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 -2
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +6 -4
- singlestoredb/config.py +116 -16
- singlestoredb/connection.py +489 -523
- singlestoredb/converters.py +275 -26
- singlestoredb/exceptions.py +30 -4
- 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/__init__.py +0 -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/connection.py +1192 -0
- singlestoredb/management/__init__.py +3 -2
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +19 -14
- singlestoredb/management/manager.py +100 -40
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +6 -8
- singlestoredb/management/utils.py +253 -4
- singlestoredb/management/workspace.py +1153 -35
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +1814 -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/FIELD_TYPE.py +32 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +713 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +388 -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/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 -117
- singlestoredb/tests/test_config.py +13 -15
- singlestoredb/tests/test_connection.py +241 -289
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +0 -2
- 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 -28
- singlestoredb/tests/test_management.py +588 -10
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -14
- singlestoredb/tests/test_types.py +0 -2
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/test_xdict.py +0 -2
- singlestoredb/tests/utils.py +3 -4
- singlestoredb/types.py +4 -5
- singlestoredb/utils/config.py +71 -12
- singlestoredb/utils/convert_rows.py +0 -2
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -3
- singlestoredb/utils/xdict.py +12 -12
- singlestoredb-1.0.3.dist-info/METADATA +139 -0
- singlestoredb-1.0.3.dist-info/RECORD +112 -0
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
- singlestoredb/drivers/__init__.py +0 -46
- singlestoredb/drivers/base.py +0 -200
- singlestoredb/drivers/cymysql.py +0 -40
- singlestoredb/drivers/http.py +0 -49
- singlestoredb/drivers/mariadb.py +0 -42
- singlestoredb/drivers/mysqlconnector.py +0 -51
- singlestoredb/drivers/mysqldb.py +0 -62
- singlestoredb/drivers/pymysql.py +0 -39
- singlestoredb/drivers/pyodbc.py +0 -67
- singlestoredb/http.py +0 -794
- singlestoredb-0.3.3.dist-info/METADATA +0 -105
- singlestoredb-0.3.3.dist-info/RECORD +0 -46
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
- {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.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
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB Cluster Management."""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
3
|
import datetime
|
|
6
4
|
import warnings
|
|
7
|
-
from collections.abc import Sequence
|
|
8
5
|
from typing import Any
|
|
9
6
|
from typing import Dict
|
|
10
7
|
from typing import List
|
|
@@ -15,6 +12,7 @@ from .. import connection
|
|
|
15
12
|
from ..exceptions import ManagementError
|
|
16
13
|
from .manager import Manager
|
|
17
14
|
from .region import Region
|
|
15
|
+
from .utils import NamedList
|
|
18
16
|
from .utils import to_datetime
|
|
19
17
|
from .utils import vars_to_str
|
|
20
18
|
|
|
@@ -41,7 +39,7 @@ class Cluster(object):
|
|
|
41
39
|
units: float, state: str, version: str,
|
|
42
40
|
created_at: Union[str, datetime.datetime],
|
|
43
41
|
expires_at: Optional[Union[str, datetime.datetime]] = None,
|
|
44
|
-
firewall_ranges: Optional[
|
|
42
|
+
firewall_ranges: Optional[List[str]] = None,
|
|
45
43
|
terminated_at: Optional[Union[str, datetime.datetime]] = None,
|
|
46
44
|
endpoint: Optional[str] = None,
|
|
47
45
|
):
|
|
@@ -94,7 +92,7 @@ class Cluster(object):
|
|
|
94
92
|
return str(self)
|
|
95
93
|
|
|
96
94
|
@classmethod
|
|
97
|
-
def from_dict(cls, obj: Dict[str, Any], manager: 'ClusterManager') -> Cluster:
|
|
95
|
+
def from_dict(cls, obj: Dict[str, Any], manager: 'ClusterManager') -> 'Cluster':
|
|
98
96
|
"""
|
|
99
97
|
Construct a Cluster from a dictionary of values.
|
|
100
98
|
|
|
@@ -123,7 +121,7 @@ class Cluster(object):
|
|
|
123
121
|
out._manager = manager
|
|
124
122
|
return out
|
|
125
123
|
|
|
126
|
-
def refresh(self) -> Cluster:
|
|
124
|
+
def refresh(self) -> 'Cluster':
|
|
127
125
|
"""Update the object to the current state."""
|
|
128
126
|
if self._manager is None:
|
|
129
127
|
raise ManagementError(
|
|
@@ -138,7 +136,7 @@ class Cluster(object):
|
|
|
138
136
|
self, name: Optional[str] = None,
|
|
139
137
|
admin_password: Optional[str] = None,
|
|
140
138
|
expires_at: Optional[str] = None,
|
|
141
|
-
size: Optional[str] = None, firewall_ranges: Optional[
|
|
139
|
+
size: Optional[str] = None, firewall_ranges: Optional[List[str]] = None,
|
|
142
140
|
) -> None:
|
|
143
141
|
"""
|
|
144
142
|
Update the cluster definition.
|
|
@@ -340,20 +338,20 @@ class ClusterManager(Manager):
|
|
|
340
338
|
obj_type = 'cluster'
|
|
341
339
|
|
|
342
340
|
@property
|
|
343
|
-
def clusters(self) ->
|
|
341
|
+
def clusters(self) -> NamedList[Cluster]:
|
|
344
342
|
"""Return a list of available clusters."""
|
|
345
343
|
res = self._get('clusters')
|
|
346
|
-
return [Cluster.from_dict(item, self) for item in res.json()]
|
|
344
|
+
return NamedList([Cluster.from_dict(item, self) for item in res.json()])
|
|
347
345
|
|
|
348
346
|
@property
|
|
349
|
-
def regions(self) ->
|
|
347
|
+
def regions(self) -> NamedList[Region]:
|
|
350
348
|
"""Return a list of available regions."""
|
|
351
349
|
res = self._get('regions')
|
|
352
|
-
return [Region.from_dict(item, self) for item in res.json()]
|
|
350
|
+
return NamedList([Region.from_dict(item, self) for item in res.json()])
|
|
353
351
|
|
|
354
352
|
def create_cluster(
|
|
355
353
|
self, name: str, region: Union[str, Region], admin_password: str,
|
|
356
|
-
firewall_ranges:
|
|
354
|
+
firewall_ranges: List[str], expires_at: Optional[str] = None,
|
|
357
355
|
size: Optional[str] = None, plan: Optional[str] = None,
|
|
358
356
|
wait_on_active: bool = False, wait_timeout: int = 600,
|
|
359
357
|
wait_interval: int = 20,
|
|
@@ -429,6 +427,8 @@ def manage_cluster(
|
|
|
429
427
|
access_token: Optional[str] = None,
|
|
430
428
|
version: str = ClusterManager.default_version,
|
|
431
429
|
base_url: str = ClusterManager.default_base_url,
|
|
430
|
+
*,
|
|
431
|
+
organization_id: Optional[str] = None,
|
|
432
432
|
) -> ClusterManager:
|
|
433
433
|
"""
|
|
434
434
|
Retrieve a SingleStoreDB cluster manager.
|
|
@@ -441,6 +441,8 @@ def manage_cluster(
|
|
|
441
441
|
Version of the API to use
|
|
442
442
|
base_url : str, optional
|
|
443
443
|
Base URL of the cluster management API
|
|
444
|
+
organization_id: str, optional
|
|
445
|
+
ID of organization, if using a JWT for authentication
|
|
444
446
|
|
|
445
447
|
Returns
|
|
446
448
|
-------
|
|
@@ -449,7 +451,10 @@ def manage_cluster(
|
|
|
449
451
|
"""
|
|
450
452
|
warnings.warn(
|
|
451
453
|
'The cluster management API is deprecated; '
|
|
452
|
-
'use
|
|
454
|
+
'use manage_workspaces instead.',
|
|
453
455
|
category=DeprecationWarning,
|
|
454
456
|
)
|
|
455
|
-
return ClusterManager(
|
|
457
|
+
return ClusterManager(
|
|
458
|
+
access_token=access_token, base_url=base_url,
|
|
459
|
+
version=version, organization_id=organization_id,
|
|
460
|
+
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB Base Manager."""
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
5
|
import time
|
|
6
6
|
from typing import Any
|
|
7
7
|
from typing import Dict
|
|
@@ -10,44 +10,73 @@ from typing import Optional
|
|
|
10
10
|
from typing import Union
|
|
11
11
|
from urllib.parse import urljoin
|
|
12
12
|
|
|
13
|
+
import jwt
|
|
13
14
|
import requests
|
|
14
15
|
|
|
15
16
|
from .. import config
|
|
16
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
|
|
17
41
|
|
|
18
42
|
|
|
19
43
|
class Manager(object):
|
|
20
44
|
"""SingleStoreDB manager base class."""
|
|
21
45
|
|
|
22
46
|
#: Management API version if none is specified.
|
|
23
|
-
default_version = '
|
|
47
|
+
default_version = config.get_option('management.version')
|
|
24
48
|
|
|
25
49
|
#: Base URL if none is specified.
|
|
26
|
-
default_base_url = '
|
|
50
|
+
default_base_url = config.get_option('management.base_url')
|
|
27
51
|
|
|
28
52
|
#: Object type
|
|
29
53
|
obj_type = ''
|
|
30
54
|
|
|
31
55
|
def __init__(
|
|
32
56
|
self, access_token: Optional[str] = None, version: Optional[str] = None,
|
|
33
|
-
base_url: Optional[str] = None,
|
|
57
|
+
base_url: Optional[str] = None, *, organization_id: Optional[str] = None,
|
|
34
58
|
):
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
from .. import __version__ as client_version
|
|
60
|
+
new_access_token = (
|
|
61
|
+
access_token or get_token()
|
|
38
62
|
)
|
|
39
|
-
if not
|
|
63
|
+
if not new_access_token:
|
|
40
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)
|
|
41
66
|
self._sess = requests.Session()
|
|
42
67
|
self._sess.headers.update({
|
|
43
|
-
'Authorization': f'Bearer {
|
|
68
|
+
'Authorization': f'Bearer {new_access_token}',
|
|
44
69
|
'Content-Type': 'application/json',
|
|
45
70
|
'Accept': 'application/json',
|
|
71
|
+
'User-Agent': f'SingleStoreDB-Python/{client_version}',
|
|
46
72
|
})
|
|
47
73
|
self._base_url = urljoin(
|
|
48
74
|
base_url or type(self).default_base_url,
|
|
49
75
|
version or type(self).default_version,
|
|
50
76
|
) + '/'
|
|
77
|
+
self._params: Dict[str, str] = {}
|
|
78
|
+
if organization_id:
|
|
79
|
+
self._params['organizationID'] = organization_id
|
|
51
80
|
|
|
52
81
|
def _check(
|
|
53
82
|
self, res: requests.Response, url: str, params: Dict[str, Any],
|
|
@@ -65,6 +94,8 @@ class Manager(object):
|
|
|
65
94
|
requests.Response
|
|
66
95
|
|
|
67
96
|
"""
|
|
97
|
+
if config.get_option('debug.queries'):
|
|
98
|
+
print(os.path.join(self._base_url, url), params, file=sys.stderr)
|
|
68
99
|
if res.status_code >= 400:
|
|
69
100
|
txt = res.text.strip()
|
|
70
101
|
msg = f'{txt}: /{url}'
|
|
@@ -72,12 +103,27 @@ class Manager(object):
|
|
|
72
103
|
new_params = params.copy()
|
|
73
104
|
if 'json' in new_params:
|
|
74
105
|
for k, v in new_params['json'].items():
|
|
75
|
-
if 'password' in k.lower():
|
|
106
|
+
if 'password' in k.lower() and v:
|
|
76
107
|
new_params['json'][k] = '*' * len(v)
|
|
77
108
|
msg += ': {}'.format(str(new_params))
|
|
78
|
-
raise ManagementError(errno=res.status_code, msg=msg)
|
|
109
|
+
raise ManagementError(errno=res.status_code, msg=msg, response=txt)
|
|
79
110
|
return res
|
|
80
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
|
+
|
|
81
127
|
def _get(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
82
128
|
"""
|
|
83
129
|
Invoke a GET request.
|
|
@@ -96,13 +142,10 @@ class Manager(object):
|
|
|
96
142
|
requests.Response
|
|
97
143
|
|
|
98
144
|
"""
|
|
99
|
-
|
|
100
|
-
self.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
),
|
|
104
|
-
path, kwargs,
|
|
105
|
-
)
|
|
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)
|
|
106
149
|
|
|
107
150
|
def _post(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
108
151
|
"""
|
|
@@ -122,13 +165,33 @@ class Manager(object):
|
|
|
122
165
|
requests.Response
|
|
123
166
|
|
|
124
167
|
"""
|
|
125
|
-
|
|
126
|
-
self.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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)
|
|
132
195
|
|
|
133
196
|
def _delete(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
134
197
|
"""
|
|
@@ -148,13 +211,10 @@ class Manager(object):
|
|
|
148
211
|
requests.Response
|
|
149
212
|
|
|
150
213
|
"""
|
|
151
|
-
|
|
152
|
-
self.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
),
|
|
156
|
-
path, kwargs,
|
|
157
|
-
)
|
|
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)
|
|
158
218
|
|
|
159
219
|
def _patch(self, path: str, *args: Any, **kwargs: Any) -> requests.Response:
|
|
160
220
|
"""
|
|
@@ -174,13 +234,10 @@ class Manager(object):
|
|
|
174
234
|
requests.Response
|
|
175
235
|
|
|
176
236
|
"""
|
|
177
|
-
|
|
178
|
-
self.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
),
|
|
182
|
-
path, kwargs,
|
|
183
|
-
)
|
|
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)
|
|
184
241
|
|
|
185
242
|
def _wait_on_state(
|
|
186
243
|
self,
|
|
@@ -217,12 +274,14 @@ class Manager(object):
|
|
|
217
274
|
x.lower().strip()
|
|
218
275
|
for x in (isinstance(state, str) and [state] or state)
|
|
219
276
|
]
|
|
277
|
+
|
|
220
278
|
if getattr(out, 'state', None) is None:
|
|
221
279
|
raise ManagementError(
|
|
222
280
|
msg='{} object does not have a `state` attribute'.format(
|
|
223
281
|
type(out).__name__,
|
|
224
282
|
),
|
|
225
283
|
)
|
|
284
|
+
|
|
226
285
|
while True:
|
|
227
286
|
if getattr(out, 'state').lower() in states:
|
|
228
287
|
break
|
|
@@ -234,4 +293,5 @@ class Manager(object):
|
|
|
234
293
|
time.sleep(interval)
|
|
235
294
|
timeout -= interval
|
|
236
295
|
out = getattr(self, f'get_{self.obj_type}')(out.id)
|
|
296
|
+
|
|
237
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
|