singlestoredb 1.8.0__cp38-abi3-win32.whl → 1.10.0__cp38-abi3-win32.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_accel.pyd +0 -0
- singlestoredb/__init__.py +2 -2
- singlestoredb/config.py +6 -0
- singlestoredb/connection.py +11 -0
- singlestoredb/fusion/handler.py +3 -7
- singlestoredb/fusion/handlers/export.py +17 -21
- singlestoredb/fusion/handlers/files.py +690 -0
- singlestoredb/fusion/handlers/stage.py +103 -91
- singlestoredb/fusion/handlers/utils.py +148 -0
- singlestoredb/magics/__init__.py +34 -0
- singlestoredb/magics/run_personal.py +56 -0
- singlestoredb/magics/run_shared.py +53 -0
- singlestoredb/management/__init__.py +1 -0
- singlestoredb/management/cluster.py +2 -1
- singlestoredb/management/export.py +20 -184
- singlestoredb/management/files.py +1039 -0
- singlestoredb/management/manager.py +3 -2
- singlestoredb/management/workspace.py +165 -351
- singlestoredb/mysql/connection.py +9 -1
- singlestoredb/tests/test.ipynb +18 -0
- singlestoredb/tests/test2.ipynb +18 -0
- singlestoredb/tests/test_management.py +273 -1
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/METADATA +1 -1
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/RECORD +30 -21
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/top_level.txt +1 -0
- sqlx/__init__.py +4 -0
- sqlx/magic.py +113 -0
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/LICENSE +0 -0
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/WHEEL +0 -0
- {singlestoredb-1.8.0.dist-info → singlestoredb-1.10.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
6
|
+
from IPython.core.magic import line_magic
|
|
7
|
+
from IPython.core.magic import Magics
|
|
8
|
+
from IPython.core.magic import magics_class
|
|
9
|
+
from IPython.core.magic import needs_local_scope
|
|
10
|
+
from IPython.core.magic import no_var_expand
|
|
11
|
+
from jinja2 import Template
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@magics_class
|
|
15
|
+
class RunPersonalMagic(Magics):
|
|
16
|
+
def __init__(self, shell: InteractiveShell):
|
|
17
|
+
Magics.__init__(self, shell=shell)
|
|
18
|
+
|
|
19
|
+
@no_var_expand
|
|
20
|
+
@needs_local_scope
|
|
21
|
+
@line_magic('run_personal')
|
|
22
|
+
def run_personal(self, line: str, local_ns: Any = None) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
Downloads a personal file using the %sql magic and then runs it using %run.
|
|
25
|
+
|
|
26
|
+
Examples::
|
|
27
|
+
|
|
28
|
+
# Line usage
|
|
29
|
+
|
|
30
|
+
%run_personal personal_file.ipynb
|
|
31
|
+
|
|
32
|
+
%run_personal {{ sample_notebook_name }}
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
template = Template(line.strip())
|
|
37
|
+
personal_file = template.render(local_ns)
|
|
38
|
+
if not personal_file:
|
|
39
|
+
raise ValueError('No personal file specified.')
|
|
40
|
+
if (personal_file.startswith("'") and personal_file.endswith("'")) or \
|
|
41
|
+
(personal_file.startswith('"') and personal_file.endswith('"')):
|
|
42
|
+
personal_file = personal_file[1:-1]
|
|
43
|
+
if not personal_file:
|
|
44
|
+
raise ValueError('No personal file specified.')
|
|
45
|
+
|
|
46
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
47
|
+
temp_file_path = os.path.join(temp_dir, personal_file)
|
|
48
|
+
sql_command = (
|
|
49
|
+
f"DOWNLOAD PERSONAL FILE '{personal_file}' "
|
|
50
|
+
f"TO '{temp_file_path}'"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Execute the SQL command
|
|
54
|
+
self.shell.run_line_magic('sql', sql_command)
|
|
55
|
+
# Run the downloaded file
|
|
56
|
+
self.shell.run_line_magic('run', f'"{temp_file_path}"')
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
6
|
+
from IPython.core.magic import line_magic
|
|
7
|
+
from IPython.core.magic import Magics
|
|
8
|
+
from IPython.core.magic import magics_class
|
|
9
|
+
from IPython.core.magic import needs_local_scope
|
|
10
|
+
from IPython.core.magic import no_var_expand
|
|
11
|
+
from jinja2 import Template
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@magics_class
|
|
15
|
+
class RunSharedMagic(Magics):
|
|
16
|
+
def __init__(self, shell: InteractiveShell):
|
|
17
|
+
Magics.__init__(self, shell=shell)
|
|
18
|
+
|
|
19
|
+
@no_var_expand
|
|
20
|
+
@needs_local_scope
|
|
21
|
+
@line_magic('run_shared')
|
|
22
|
+
def run_shared(self, line: str, local_ns: Any = None) -> Any:
|
|
23
|
+
"""
|
|
24
|
+
Downloads a shared file using the %sql magic and then runs it using %run.
|
|
25
|
+
|
|
26
|
+
Examples::
|
|
27
|
+
|
|
28
|
+
# Line usage
|
|
29
|
+
|
|
30
|
+
%run_shared shared_file.ipynb
|
|
31
|
+
|
|
32
|
+
%run_shared {{ sample_notebook_name }}
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
template = Template(line.strip())
|
|
37
|
+
shared_file = template.render(local_ns)
|
|
38
|
+
if not shared_file:
|
|
39
|
+
raise ValueError('No shared file specified.')
|
|
40
|
+
if (shared_file.startswith("'") and shared_file.endswith("'")) or \
|
|
41
|
+
(shared_file.startswith('"') and shared_file.endswith('"')):
|
|
42
|
+
shared_file = shared_file[1:-1]
|
|
43
|
+
if not shared_file:
|
|
44
|
+
raise ValueError('No personal file specified.')
|
|
45
|
+
|
|
46
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
47
|
+
temp_file_path = os.path.join(temp_dir, shared_file)
|
|
48
|
+
sql_command = f"DOWNLOAD SHARED FILE '{shared_file}' TO '{temp_file_path}'"
|
|
49
|
+
|
|
50
|
+
# Execute the SQL command
|
|
51
|
+
self.shell.run_line_magic('sql', sql_command)
|
|
52
|
+
# Run the downloaded file
|
|
53
|
+
self.shell.run_line_magic('run', f'"{temp_file_path}"')
|
|
@@ -333,7 +333,8 @@ class ClusterManager(Manager):
|
|
|
333
333
|
default_version = 'v0beta'
|
|
334
334
|
|
|
335
335
|
#: Base URL if none is specified.
|
|
336
|
-
default_base_url = config.get_option('management.base_url')
|
|
336
|
+
default_base_url = config.get_option('management.base_url') \
|
|
337
|
+
or 'https://api.singlestore.com'
|
|
337
338
|
|
|
338
339
|
#: Object type
|
|
339
340
|
obj_type = 'cluster'
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
"""SingleStoreDB export service."""
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
7
|
from typing import Any
|
|
8
8
|
from typing import Dict
|
|
9
9
|
from typing import List
|
|
10
10
|
from typing import Optional
|
|
11
|
+
from typing import Union
|
|
11
12
|
|
|
12
13
|
from .. import ManagementError
|
|
13
14
|
from .utils import vars_to_str
|
|
@@ -15,172 +16,13 @@ from .workspace import WorkspaceGroup
|
|
|
15
16
|
from .workspace import WorkspaceManager
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class Link(object):
|
|
19
|
-
"""Generic storage base class."""
|
|
20
|
-
scheme: str = 'unknown'
|
|
21
|
-
|
|
22
|
-
def __str__(self) -> str:
|
|
23
|
-
"""Return string representation."""
|
|
24
|
-
return vars_to_str(self)
|
|
25
|
-
|
|
26
|
-
def __repr__(self) -> str:
|
|
27
|
-
"""Return string representation."""
|
|
28
|
-
return str(self)
|
|
29
|
-
|
|
30
|
-
@abc.abstractmethod
|
|
31
|
-
def to_storage_location(self) -> Dict[str, Any]:
|
|
32
|
-
raise NotImplementedError
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def from_config_and_creds(
|
|
36
|
-
cls,
|
|
37
|
-
scheme: str,
|
|
38
|
-
config: Dict[str, Any],
|
|
39
|
-
credentials: Dict[str, Any],
|
|
40
|
-
manager: 'WorkspaceManager',
|
|
41
|
-
) -> 'Link':
|
|
42
|
-
out_cls = None
|
|
43
|
-
for c in cls.__subclasses__():
|
|
44
|
-
if c.scheme == scheme.upper():
|
|
45
|
-
out_cls = c
|
|
46
|
-
break
|
|
47
|
-
|
|
48
|
-
if out_cls is None:
|
|
49
|
-
raise TypeError(f'No link class found for given information: {scheme}')
|
|
50
|
-
|
|
51
|
-
return out_cls.from_config_and_creds(scheme, config, credentials, manager)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class S3Link(Link):
|
|
55
|
-
"""S3 link."""
|
|
56
|
-
|
|
57
|
-
scheme: str = 'S3'
|
|
58
|
-
region: str
|
|
59
|
-
storage_base_url: str
|
|
60
|
-
|
|
61
|
-
def __init__(self, region: str, storage_base_url: str):
|
|
62
|
-
self.region = region
|
|
63
|
-
self.storage_base_url = storage_base_url
|
|
64
|
-
self._manager: Optional[WorkspaceManager] = None
|
|
65
|
-
|
|
66
|
-
def to_storage_location(self) -> Dict[str, Any]:
|
|
67
|
-
return dict(
|
|
68
|
-
storageBaseURL=self.storage_base_url,
|
|
69
|
-
storageRegion=self.region,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
@classmethod
|
|
73
|
-
def from_config_and_creds(
|
|
74
|
-
cls,
|
|
75
|
-
scheme: str,
|
|
76
|
-
config: Dict[str, Any],
|
|
77
|
-
credentials: Dict[str, Any],
|
|
78
|
-
manager: 'WorkspaceManager',
|
|
79
|
-
) -> 'S3Link':
|
|
80
|
-
assert scheme.upper() == cls.scheme
|
|
81
|
-
|
|
82
|
-
params: Dict[str, Any] = {}
|
|
83
|
-
params.update(config)
|
|
84
|
-
params.update(credentials)
|
|
85
|
-
|
|
86
|
-
assert params.get('region'), 'region is required'
|
|
87
|
-
assert params.get('endpoint_url'), 'endpoint_url is required'
|
|
88
|
-
|
|
89
|
-
out = cls(params['region'], params['endpoint_url'])
|
|
90
|
-
out._manager = manager
|
|
91
|
-
return out
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class Catalog(object):
|
|
95
|
-
"""Generic catalog base class."""
|
|
96
|
-
|
|
97
|
-
catalog_type: str = 'UNKNOWN'
|
|
98
|
-
table_format: str = 'UNKNOWN'
|
|
99
|
-
|
|
100
|
-
def __str__(self) -> str:
|
|
101
|
-
"""Return string representation."""
|
|
102
|
-
return vars_to_str(self)
|
|
103
|
-
|
|
104
|
-
def __repr__(self) -> str:
|
|
105
|
-
"""Return string representation."""
|
|
106
|
-
return str(self)
|
|
107
|
-
|
|
108
|
-
@classmethod
|
|
109
|
-
def from_config_and_creds(
|
|
110
|
-
cls,
|
|
111
|
-
config: Dict[str, Any],
|
|
112
|
-
credentials: Dict[str, Any],
|
|
113
|
-
manager: 'WorkspaceManager',
|
|
114
|
-
) -> 'Catalog':
|
|
115
|
-
catalog_type = config['type'].upper()
|
|
116
|
-
table_format = config['table_format'].upper()
|
|
117
|
-
|
|
118
|
-
out_cls = None
|
|
119
|
-
for c in cls.__subclasses__():
|
|
120
|
-
if c.catalog_type == catalog_type and c.table_format == table_format:
|
|
121
|
-
out_cls = c
|
|
122
|
-
break
|
|
123
|
-
|
|
124
|
-
if out_cls is None:
|
|
125
|
-
raise TypeError(f'No catalog class found for given information: {config}')
|
|
126
|
-
|
|
127
|
-
return out_cls.from_config_and_creds(config, credentials, manager)
|
|
128
|
-
|
|
129
|
-
@abc.abstractmethod
|
|
130
|
-
def to_catalog_info(self) -> Dict[str, Any]:
|
|
131
|
-
"""Return a catalog info dictionary."""
|
|
132
|
-
raise NotImplementedError
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class IcebergGlueCatalog(Catalog):
|
|
136
|
-
"""Iceberg glue catalog."""
|
|
137
|
-
|
|
138
|
-
table_format = 'ICEBERG'
|
|
139
|
-
catalog_type = 'GLUE'
|
|
140
|
-
|
|
141
|
-
region: str
|
|
142
|
-
catalog_id: str
|
|
143
|
-
|
|
144
|
-
def __init__(self, region: str, catalog_id: str):
|
|
145
|
-
self.region = region
|
|
146
|
-
self.catalog_id = catalog_id
|
|
147
|
-
self._manager: Optional[WorkspaceManager] = None
|
|
148
|
-
|
|
149
|
-
@classmethod
|
|
150
|
-
def from_config_and_creds(
|
|
151
|
-
cls,
|
|
152
|
-
config: Dict[str, Any],
|
|
153
|
-
credentials: Dict[str, Any],
|
|
154
|
-
manager: 'WorkspaceManager',
|
|
155
|
-
) -> 'IcebergGlueCatalog':
|
|
156
|
-
params = {}
|
|
157
|
-
params.update(config)
|
|
158
|
-
params.update(credentials)
|
|
159
|
-
|
|
160
|
-
out = cls(
|
|
161
|
-
region=params['region'],
|
|
162
|
-
catalog_id=params['id'],
|
|
163
|
-
)
|
|
164
|
-
out._manager = manager
|
|
165
|
-
return out
|
|
166
|
-
|
|
167
|
-
def to_catalog_info(self) -> Dict[str, Any]:
|
|
168
|
-
"""Return a catalog info dictionary."""
|
|
169
|
-
return dict(
|
|
170
|
-
catalogSource=self.catalog_type,
|
|
171
|
-
tableFormat=self.table_format,
|
|
172
|
-
glueRegion=self.region,
|
|
173
|
-
glueCatalogID=self.catalog_id,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
19
|
class ExportService(object):
|
|
178
20
|
"""Export service."""
|
|
179
21
|
|
|
180
22
|
database: str
|
|
181
23
|
table: str
|
|
182
|
-
|
|
183
|
-
|
|
24
|
+
catalog_info: Dict[str, Any]
|
|
25
|
+
storage_info: Dict[str, Any]
|
|
184
26
|
columns: Optional[List[str]]
|
|
185
27
|
|
|
186
28
|
def __init__(
|
|
@@ -188,8 +30,8 @@ class ExportService(object):
|
|
|
188
30
|
workspace_group: WorkspaceGroup,
|
|
189
31
|
database: str,
|
|
190
32
|
table: str,
|
|
191
|
-
|
|
192
|
-
|
|
33
|
+
catalog_info: Union[str, Dict[str, Any]],
|
|
34
|
+
storage_info: Union[str, Dict[str, Any]],
|
|
193
35
|
columns: Optional[List[str]],
|
|
194
36
|
):
|
|
195
37
|
#: Workspace group
|
|
@@ -205,10 +47,16 @@ class ExportService(object):
|
|
|
205
47
|
self.columns = columns
|
|
206
48
|
|
|
207
49
|
#: Catalog
|
|
208
|
-
|
|
50
|
+
if isinstance(catalog_info, str):
|
|
51
|
+
self.catalog_info = json.loads(catalog_info)
|
|
52
|
+
else:
|
|
53
|
+
self.catalog_info = copy.copy(catalog_info)
|
|
209
54
|
|
|
210
55
|
#: Storage
|
|
211
|
-
|
|
56
|
+
if isinstance(storage_info, str):
|
|
57
|
+
self.storage_info = json.loads(storage_info)
|
|
58
|
+
else:
|
|
59
|
+
self.storage_info = copy.copy(storage_info)
|
|
212
60
|
|
|
213
61
|
self._manager: Optional[WorkspaceManager] = workspace_group._manager
|
|
214
62
|
|
|
@@ -227,21 +75,12 @@ class ExportService(object):
|
|
|
227
75
|
msg='No workspace manager is associated with this object.',
|
|
228
76
|
)
|
|
229
77
|
|
|
230
|
-
if not isinstance(self.catalog, IcebergGlueCatalog):
|
|
231
|
-
raise TypeError('Only Iceberg Glue catalog is supported at this time.')
|
|
232
|
-
|
|
233
|
-
if not isinstance(self.storage_link, S3Link):
|
|
234
|
-
raise TypeError('Only S3 links are supported at this time.')
|
|
235
|
-
|
|
236
78
|
out = self._manager._post(
|
|
237
79
|
f'workspaceGroups/{self.workspace_group.id}/'
|
|
238
80
|
'egress/createEgressClusterIdentity',
|
|
239
81
|
json=dict(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
)[1],
|
|
243
|
-
glueRegion=self.catalog.region,
|
|
244
|
-
glueCatalogID=self.catalog.catalog_id,
|
|
82
|
+
catalogInfo=self.catalog_info,
|
|
83
|
+
storageInfo=self.storage_info,
|
|
245
84
|
),
|
|
246
85
|
)
|
|
247
86
|
|
|
@@ -254,16 +93,13 @@ class ExportService(object):
|
|
|
254
93
|
msg='No workspace manager is associated with this object.',
|
|
255
94
|
)
|
|
256
95
|
|
|
257
|
-
if not isinstance(self.storage_link, S3Link):
|
|
258
|
-
raise TypeError('Only S3 links are supported at this time.')
|
|
259
|
-
|
|
260
96
|
out = self._manager._post(
|
|
261
97
|
f'workspaceGroups/{self.workspace_group.id}/egress/startTableEgress',
|
|
262
98
|
json=dict(
|
|
263
99
|
databaseName=self.database,
|
|
264
100
|
tableName=self.table,
|
|
265
|
-
|
|
266
|
-
catalogInfo=self.
|
|
101
|
+
storageInfo=self.storage_info,
|
|
102
|
+
catalogInfo=self.catalog_info,
|
|
267
103
|
),
|
|
268
104
|
)
|
|
269
105
|
|
|
@@ -286,7 +122,7 @@ class ExportStatus(object):
|
|
|
286
122
|
msg='No workspace manager is associated with this object.',
|
|
287
123
|
)
|
|
288
124
|
|
|
289
|
-
out = self._manager.
|
|
125
|
+
out = self._manager._get(
|
|
290
126
|
f'workspaceGroups/{self.workspace_group.id}/egress/tableEgressStatus',
|
|
291
127
|
json=dict(egressID=self.export_id),
|
|
292
128
|
)
|