singlestoredb 0.9.0__cp36-abi3-win_amd64.whl → 0.9.2__cp36-abi3-win_amd64.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 +1 -1
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/handler.py +542 -0
- singlestoredb/fusion/handlers/__init__.py +0 -0
- singlestoredb/fusion/handlers/workspace.py +361 -0
- singlestoredb/fusion/registry.py +112 -0
- singlestoredb/fusion/result.py +120 -0
- singlestoredb/http/connection.py +65 -12
- singlestoredb/management/cluster.py +5 -4
- singlestoredb/management/utils.py +46 -0
- singlestoredb/management/workspace.py +58 -10
- singlestoredb/mysql/connection.py +10 -4
- singlestoredb/mysql/constants/FIELD_TYPE.py +1 -0
- singlestoredb/tests/test_management.py +96 -2
- singlestoredb/tests/test_results.py +6 -6
- singlestoredb/utils/mogrify.py +151 -0
- {singlestoredb-0.9.0.dist-info → singlestoredb-0.9.2.dist-info}/METADATA +2 -1
- {singlestoredb-0.9.0.dist-info → singlestoredb-0.9.2.dist-info}/RECORD +22 -15
- {singlestoredb-0.9.0.dist-info → singlestoredb-0.9.2.dist-info}/LICENSE +0 -0
- {singlestoredb-0.9.0.dist-info → singlestoredb-0.9.2.dist-info}/WHEEL +0 -0
- {singlestoredb-0.9.0.dist-info → singlestoredb-0.9.2.dist-info}/top_level.txt +0 -0
|
@@ -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,
|
|
@@ -8,6 +8,8 @@ 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 SupportsIndex
|
|
12
|
+
from typing import TypeVar
|
|
11
13
|
from typing import Union
|
|
12
14
|
|
|
13
15
|
from .. import converters
|
|
@@ -15,6 +17,7 @@ from .. import converters
|
|
|
15
17
|
JSON = Union[str, List[str], Dict[str, 'JSON']]
|
|
16
18
|
JSONObj = Dict[str, JSON]
|
|
17
19
|
JSONList = List[JSON]
|
|
20
|
+
T = TypeVar('T')
|
|
18
21
|
|
|
19
22
|
if sys.version_info < (3, 10):
|
|
20
23
|
PathLike = Union[str, os.PathLike]
|
|
@@ -24,6 +27,49 @@ else:
|
|
|
24
27
|
PathLikeABC = os.PathLike[str]
|
|
25
28
|
|
|
26
29
|
|
|
30
|
+
class NamedList(List[T]):
|
|
31
|
+
"""List class which also allows selection by ``name`` and ``id`` attribute."""
|
|
32
|
+
|
|
33
|
+
def _find_item(self, key: str) -> T:
|
|
34
|
+
for item in self:
|
|
35
|
+
if getattr(item, 'name', '') == key:
|
|
36
|
+
return item
|
|
37
|
+
if getattr(item, 'id', '') == key:
|
|
38
|
+
return item
|
|
39
|
+
raise KeyError(key)
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, key: Union[SupportsIndex, slice, str]) -> Any:
|
|
42
|
+
if isinstance(key, str):
|
|
43
|
+
return self._find_item(key)
|
|
44
|
+
return super().__getitem__(key)
|
|
45
|
+
|
|
46
|
+
def __contains__(self, key: Any) -> bool:
|
|
47
|
+
if isinstance(key, str):
|
|
48
|
+
try:
|
|
49
|
+
self._find_item(key)
|
|
50
|
+
return True
|
|
51
|
+
except KeyError:
|
|
52
|
+
return False
|
|
53
|
+
return super().__contains__(key)
|
|
54
|
+
|
|
55
|
+
def names(self) -> List[str]:
|
|
56
|
+
"""Return ``name`` attribute of each item."""
|
|
57
|
+
return [y for y in [getattr(x, 'name', None) for x in self] if y is not None]
|
|
58
|
+
|
|
59
|
+
def ids(self) -> List[str]:
|
|
60
|
+
"""Return ``id`` attribute of each item."""
|
|
61
|
+
return [y for y in [getattr(x, 'id', None) for x in self] if y is not None]
|
|
62
|
+
|
|
63
|
+
def get(self, name_or_id: str, *default: Any) -> Any:
|
|
64
|
+
"""Return object with name / ID if it exists, or return default value."""
|
|
65
|
+
try:
|
|
66
|
+
return self._find_item(name_or_id)
|
|
67
|
+
except KeyError:
|
|
68
|
+
if default:
|
|
69
|
+
return default[0]
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
|
|
27
73
|
def enable_http_tracing() -> None:
|
|
28
74
|
"""Enable tracing of HTTP requests."""
|
|
29
75
|
import logging
|
|
@@ -23,6 +23,7 @@ from .manager import Manager
|
|
|
23
23
|
from .organization import Organization
|
|
24
24
|
from .region import Region
|
|
25
25
|
from .utils import from_datetime
|
|
26
|
+
from .utils import NamedList
|
|
26
27
|
from .utils import PathLike
|
|
27
28
|
from .utils import snake_to_camel
|
|
28
29
|
from .utils import to_datetime
|
|
@@ -126,6 +127,47 @@ class StagesObject(object):
|
|
|
126
127
|
"""Return string representation."""
|
|
127
128
|
return str(self)
|
|
128
129
|
|
|
130
|
+
def open(
|
|
131
|
+
self,
|
|
132
|
+
mode: str = 'r',
|
|
133
|
+
encoding: Optional[str] = None,
|
|
134
|
+
) -> Union[io.StringIO, io.BytesIO]:
|
|
135
|
+
"""
|
|
136
|
+
Open a Stage path for reading or writing.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
mode : str, optional
|
|
141
|
+
The read / write mode. The following modes are supported:
|
|
142
|
+
* 'r' open for reading (default)
|
|
143
|
+
* 'w' open for writing, truncating the file first
|
|
144
|
+
* 'x' create a new file and open it for writing
|
|
145
|
+
The data type can be specified by adding one of the following:
|
|
146
|
+
* 'b' binary mode
|
|
147
|
+
* 't' text mode (default)
|
|
148
|
+
encoding : str, optional
|
|
149
|
+
The string encoding to use for text
|
|
150
|
+
|
|
151
|
+
Returns
|
|
152
|
+
-------
|
|
153
|
+
StagesObjectBytesReader - 'rb' or 'b' mode
|
|
154
|
+
StagesObjectBytesWriter - 'wb' or 'xb' mode
|
|
155
|
+
StagesObjectTextReader - 'r' or 'rt' mode
|
|
156
|
+
StagesObjectTextWriter - 'w', 'x', 'wt' or 'xt' mode
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
if self._stages is None:
|
|
160
|
+
raise ManagementError(
|
|
161
|
+
msg='No Stages object is associated with this object.',
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if self.is_dir():
|
|
165
|
+
raise IsADirectoryError(
|
|
166
|
+
f'directories can not be read or written: {self.path}',
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return self._stages.open(self.path, mode=mode, encoding=encoding)
|
|
170
|
+
|
|
129
171
|
def download(
|
|
130
172
|
self,
|
|
131
173
|
local_path: Optional[PathLike] = None,
|
|
@@ -256,7 +298,6 @@ class StagesObjectTextWriter(io.StringIO):
|
|
|
256
298
|
|
|
257
299
|
def close(self) -> None:
|
|
258
300
|
"""Write the content to the stage path."""
|
|
259
|
-
print('CLOSING')
|
|
260
301
|
self._stages._upload(self.getvalue(), self._stage_path)
|
|
261
302
|
super().close()
|
|
262
303
|
|
|
@@ -653,13 +694,18 @@ class Stages(object):
|
|
|
653
694
|
return out
|
|
654
695
|
return [x['path'] for x in res['content'] or []]
|
|
655
696
|
|
|
656
|
-
def listdir(
|
|
697
|
+
def listdir(
|
|
698
|
+
self,
|
|
699
|
+
stage_path: PathLike = '/',
|
|
700
|
+
*,
|
|
701
|
+
recursive: bool = False,
|
|
702
|
+
) -> List[str]:
|
|
657
703
|
"""
|
|
658
704
|
List the files / folders at the given path.
|
|
659
705
|
|
|
660
706
|
Parameters
|
|
661
707
|
----------
|
|
662
|
-
stage_path : Path or str
|
|
708
|
+
stage_path : Path or str, optional
|
|
663
709
|
Path to the stage location
|
|
664
710
|
|
|
665
711
|
Returns
|
|
@@ -1163,14 +1209,16 @@ class WorkspaceGroup(object):
|
|
|
1163
1209
|
)
|
|
1164
1210
|
|
|
1165
1211
|
@property
|
|
1166
|
-
def workspaces(self) ->
|
|
1212
|
+
def workspaces(self) -> NamedList[Workspace]:
|
|
1167
1213
|
"""Return a list of available workspaces."""
|
|
1168
1214
|
if self._manager is None:
|
|
1169
1215
|
raise ManagementError(
|
|
1170
1216
|
msg='No workspace manager is associated with this object.',
|
|
1171
1217
|
)
|
|
1172
1218
|
res = self._manager._get('workspaces', params=dict(workspaceGroupID=self.id))
|
|
1173
|
-
return
|
|
1219
|
+
return NamedList(
|
|
1220
|
+
[Workspace.from_dict(item, self._manager) for item in res.json()],
|
|
1221
|
+
)
|
|
1174
1222
|
|
|
1175
1223
|
|
|
1176
1224
|
class Billing(object):
|
|
@@ -1275,10 +1323,10 @@ class WorkspaceManager(Manager):
|
|
|
1275
1323
|
obj_type = 'workspace'
|
|
1276
1324
|
|
|
1277
1325
|
@ property
|
|
1278
|
-
def workspace_groups(self) ->
|
|
1326
|
+
def workspace_groups(self) -> NamedList[WorkspaceGroup]:
|
|
1279
1327
|
"""Return a list of available workspace groups."""
|
|
1280
1328
|
res = self._get('workspaceGroups')
|
|
1281
|
-
return [WorkspaceGroup.from_dict(item, self) for item in res.json()]
|
|
1329
|
+
return NamedList([WorkspaceGroup.from_dict(item, self) for item in res.json()])
|
|
1282
1330
|
|
|
1283
1331
|
@ property
|
|
1284
1332
|
def organizations(self) -> Organizations:
|
|
@@ -1291,10 +1339,10 @@ class WorkspaceManager(Manager):
|
|
|
1291
1339
|
return Billing(self)
|
|
1292
1340
|
|
|
1293
1341
|
@ property
|
|
1294
|
-
def regions(self) ->
|
|
1342
|
+
def regions(self) -> NamedList[Region]:
|
|
1295
1343
|
"""Return a list of available regions."""
|
|
1296
1344
|
res = self._get('regions')
|
|
1297
|
-
return [Region.from_dict(item, self) for item in res.json()]
|
|
1345
|
+
return NamedList([Region.from_dict(item, self) for item in res.json()])
|
|
1298
1346
|
|
|
1299
1347
|
def create_workspace_group(
|
|
1300
1348
|
self, name: str, region: Union[str, Region],
|
|
@@ -1341,7 +1389,7 @@ class WorkspaceManager(Manager):
|
|
|
1341
1389
|
'workspaceGroups', json=dict(
|
|
1342
1390
|
name=name, regionID=region,
|
|
1343
1391
|
adminPassword=admin_password,
|
|
1344
|
-
firewallRanges=firewall_ranges,
|
|
1392
|
+
firewallRanges=firewall_ranges or [],
|
|
1345
1393
|
expiresAt=expires_at,
|
|
1346
1394
|
allowAllTraffic=allow_all_traffic,
|
|
1347
1395
|
updateWindow=update_window,
|
|
@@ -47,6 +47,7 @@ from .protocol import (
|
|
|
47
47
|
)
|
|
48
48
|
from . import err
|
|
49
49
|
from ..config import get_option
|
|
50
|
+
from .. import fusion
|
|
50
51
|
from ..connection import Connection as BaseConnection
|
|
51
52
|
|
|
52
53
|
try:
|
|
@@ -758,10 +759,15 @@ class Connection(BaseConnection):
|
|
|
758
759
|
"""
|
|
759
760
|
# if DEBUG:
|
|
760
761
|
# print("DEBUG: sending query:", sql)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
762
|
+
handler = fusion.get_handler(sql)
|
|
763
|
+
if handler is not None:
|
|
764
|
+
self._result = fusion.execute(self, sql, handler=handler)
|
|
765
|
+
self._affected_rows = self._result.affected_rows
|
|
766
|
+
else:
|
|
767
|
+
if isinstance(sql, str):
|
|
768
|
+
sql = sql.encode(self.encoding, 'surrogateescape')
|
|
769
|
+
self._execute_command(COMMAND.COM_QUERY, sql)
|
|
770
|
+
self._affected_rows = self._read_query_result(unbuffered=unbuffered)
|
|
765
771
|
return self._affected_rows
|
|
766
772
|
|
|
767
773
|
def next_result(self, unbuffered=False):
|
|
@@ -69,10 +69,22 @@ class TestCluster(unittest.TestCase):
|
|
|
69
69
|
def test_regions(self):
|
|
70
70
|
out = self.manager.regions
|
|
71
71
|
providers = {x.provider for x in out}
|
|
72
|
+
names = [x.name for x in out]
|
|
73
|
+
ids = [x.id for x in out]
|
|
72
74
|
assert 'Azure' in providers, providers
|
|
73
75
|
assert 'GCP' in providers, providers
|
|
74
76
|
assert 'AWS' in providers, providers
|
|
75
77
|
|
|
78
|
+
objs = {}
|
|
79
|
+
for item in out:
|
|
80
|
+
objs[item.id] = item
|
|
81
|
+
objs[item.name] = item
|
|
82
|
+
|
|
83
|
+
name = random.choice(names)
|
|
84
|
+
assert out[name] == objs[name]
|
|
85
|
+
id = random.choice(ids)
|
|
86
|
+
assert out[id] == objs[id]
|
|
87
|
+
|
|
76
88
|
def test_clusters(self):
|
|
77
89
|
clusters = self.manager.clusters
|
|
78
90
|
ids = [x.id for x in clusters]
|
|
@@ -232,19 +244,61 @@ class TestWorkspace(unittest.TestCase):
|
|
|
232
244
|
def test_regions(self):
|
|
233
245
|
out = self.manager.regions
|
|
234
246
|
providers = {x.provider for x in out}
|
|
247
|
+
names = [x.name for x in out]
|
|
248
|
+
ids = [x.id for x in out]
|
|
235
249
|
assert 'Azure' in providers, providers
|
|
236
250
|
assert 'GCP' in providers, providers
|
|
237
251
|
assert 'AWS' in providers, providers
|
|
238
252
|
|
|
253
|
+
objs = {}
|
|
254
|
+
for item in out:
|
|
255
|
+
objs[item.id] = item
|
|
256
|
+
objs[item.name] = item
|
|
257
|
+
|
|
258
|
+
name = random.choice(names)
|
|
259
|
+
assert out[name] == objs[name]
|
|
260
|
+
id = random.choice(ids)
|
|
261
|
+
assert out[id] == objs[id]
|
|
262
|
+
|
|
239
263
|
def test_workspace_groups(self):
|
|
240
264
|
workspace_groups = self.manager.workspace_groups
|
|
241
265
|
ids = [x.id for x in workspace_groups]
|
|
242
|
-
|
|
266
|
+
names = [x.name for x in workspace_groups]
|
|
267
|
+
assert self.workspace_group.id in ids
|
|
268
|
+
assert self.workspace_group.name in names
|
|
269
|
+
|
|
270
|
+
assert workspace_groups.ids() == ids
|
|
271
|
+
assert workspace_groups.names() == names
|
|
272
|
+
|
|
273
|
+
objs = {}
|
|
274
|
+
for item in workspace_groups:
|
|
275
|
+
objs[item.id] = item
|
|
276
|
+
objs[item.name] = item
|
|
277
|
+
|
|
278
|
+
name = random.choice(names)
|
|
279
|
+
assert workspace_groups[name] == objs[name]
|
|
280
|
+
id = random.choice(ids)
|
|
281
|
+
assert workspace_groups[id] == objs[id]
|
|
243
282
|
|
|
244
283
|
def test_workspaces(self):
|
|
245
284
|
spaces = self.workspace_group.workspaces
|
|
246
285
|
ids = [x.id for x in spaces]
|
|
247
|
-
|
|
286
|
+
names = [x.name for x in spaces]
|
|
287
|
+
assert self.workspace.id in ids
|
|
288
|
+
assert self.workspace.name in names
|
|
289
|
+
|
|
290
|
+
assert spaces.ids() == ids
|
|
291
|
+
assert spaces.names() == names
|
|
292
|
+
|
|
293
|
+
objs = {}
|
|
294
|
+
for item in spaces:
|
|
295
|
+
objs[item.id] = item
|
|
296
|
+
objs[item.name] = item
|
|
297
|
+
|
|
298
|
+
name = random.choice(names)
|
|
299
|
+
assert spaces[name] == objs[name]
|
|
300
|
+
id = random.choice(ids)
|
|
301
|
+
assert spaces[id] == objs[id]
|
|
248
302
|
|
|
249
303
|
def test_get_workspace_group(self):
|
|
250
304
|
group = self.manager.get_workspace_group(self.workspace_group.id)
|
|
@@ -439,6 +493,46 @@ class TestStages(unittest.TestCase):
|
|
|
439
493
|
|
|
440
494
|
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
441
495
|
|
|
496
|
+
def test_obj_open(self):
|
|
497
|
+
st = self.wg.stages
|
|
498
|
+
|
|
499
|
+
# Load test file
|
|
500
|
+
f = st.upload_file(TEST_DIR / 'test.sql', 'obj_open_test.sql')
|
|
501
|
+
|
|
502
|
+
# Read file using `open`
|
|
503
|
+
with f.open() as rfile:
|
|
504
|
+
assert rfile.read() == open(TEST_DIR / 'test.sql').read()
|
|
505
|
+
|
|
506
|
+
# Make sure directories error out
|
|
507
|
+
d = st.mkdir('obj_open_dir')
|
|
508
|
+
with self.assertRaises(IsADirectoryError):
|
|
509
|
+
d.open()
|
|
510
|
+
|
|
511
|
+
# Write file using `open`
|
|
512
|
+
with f.open('w', encoding='utf-8') as wfile:
|
|
513
|
+
wfile.write(open(TEST_DIR / 'test2.sql').read())
|
|
514
|
+
|
|
515
|
+
assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.sql').read()
|
|
516
|
+
|
|
517
|
+
# Test writer without context manager
|
|
518
|
+
wfile = f.open('w')
|
|
519
|
+
for line in open(TEST_DIR / 'test.sql'):
|
|
520
|
+
wfile.write(line)
|
|
521
|
+
wfile.close()
|
|
522
|
+
|
|
523
|
+
txt = st.download(f.path, encoding='utf-8')
|
|
524
|
+
|
|
525
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
526
|
+
|
|
527
|
+
# Test reader without context manager
|
|
528
|
+
rfile = f.open('r')
|
|
529
|
+
txt = ''
|
|
530
|
+
for line in rfile:
|
|
531
|
+
txt += line
|
|
532
|
+
rfile.close()
|
|
533
|
+
|
|
534
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
535
|
+
|
|
442
536
|
def test_os_directories(self):
|
|
443
537
|
st = self.wg.stages
|
|
444
538
|
|
|
@@ -51,7 +51,7 @@ class TestResults(unittest.TestCase):
|
|
|
51
51
|
with conn.cursor() as cur:
|
|
52
52
|
cur.execute('select * from data')
|
|
53
53
|
out = cur.fetchone()
|
|
54
|
-
assert type(out)
|
|
54
|
+
assert type(out) is tuple, type(out)
|
|
55
55
|
assert len(out) == 3, len(out)
|
|
56
56
|
cur.fetchall()
|
|
57
57
|
|
|
@@ -59,7 +59,7 @@ class TestResults(unittest.TestCase):
|
|
|
59
59
|
out = cur.fetchall()
|
|
60
60
|
assert len(out) == 5, len(out)
|
|
61
61
|
assert len(out[0]) == 3, len(out[0])
|
|
62
|
-
assert type(out[0])
|
|
62
|
+
assert type(out[0]) is tuple, type(out[0])
|
|
63
63
|
assert sorted(out) == sorted([
|
|
64
64
|
('a', 'antelopes', 2),
|
|
65
65
|
('b', 'bears', 2),
|
|
@@ -111,13 +111,13 @@ class TestResults(unittest.TestCase):
|
|
|
111
111
|
with conn.cursor() as cur:
|
|
112
112
|
cur.execute('select * from data')
|
|
113
113
|
out = cur.fetchone()
|
|
114
|
-
assert type(out)
|
|
114
|
+
assert type(out) is dict, type(out)
|
|
115
115
|
assert len(out) == 3, len(out)
|
|
116
116
|
cur.fetchall()
|
|
117
117
|
|
|
118
118
|
cur.execute('select * from data')
|
|
119
119
|
out = cur.fetchall()
|
|
120
|
-
assert type(out[0])
|
|
120
|
+
assert type(out[0]) is dict, type(out[0])
|
|
121
121
|
assert len(out) == 5, len(out)
|
|
122
122
|
assert len(out[0]) == 3, len(out[0])
|
|
123
123
|
assert sorted(out, key=lambda x: x['id']) == sorted(
|
|
@@ -139,13 +139,13 @@ class TestResults(unittest.TestCase):
|
|
|
139
139
|
with conn.cursor() as cur:
|
|
140
140
|
cur.execute('select * from data')
|
|
141
141
|
out = cur.fetchone()
|
|
142
|
-
assert type(out)
|
|
142
|
+
assert type(out) is pd.DataFrame, type(out)
|
|
143
143
|
assert len(out) == 1, len(out)
|
|
144
144
|
cur.fetchall()
|
|
145
145
|
|
|
146
146
|
cur.execute('select * from data')
|
|
147
147
|
out = cur.fetchall()
|
|
148
|
-
assert type(out)
|
|
148
|
+
assert type(out) is pd.DataFrame, type(out)
|
|
149
149
|
assert len(out) == 5, len(out)
|
|
150
150
|
out = out.sort_values('id').reset_index(drop=True)
|
|
151
151
|
exp = pd.DataFrame(
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from typing import Any
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from ..mysql import converters
|
|
9
|
+
from ..mysql.constants import SERVER_STATUS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Encoders = converters.Encoders
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def escape(
|
|
16
|
+
obj: Any,
|
|
17
|
+
charset: str = 'utf8',
|
|
18
|
+
mapping: Optional[Encoders] = None,
|
|
19
|
+
server_status: int = 0,
|
|
20
|
+
binary_prefix: bool = False,
|
|
21
|
+
) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Escape whatever value is passed.
|
|
24
|
+
|
|
25
|
+
Non-standard, for internal use; do not use this in your applications.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
dtype = type(obj)
|
|
29
|
+
if dtype is str or isinstance(obj, str):
|
|
30
|
+
return "'{}'".format(escape_string(obj, server_status=server_status))
|
|
31
|
+
if dtype is bytes or dtype is bytearray or isinstance(obj, (bytes, bytearray)):
|
|
32
|
+
return _quote_bytes(
|
|
33
|
+
obj,
|
|
34
|
+
server_status=server_status,
|
|
35
|
+
binary_prefix=binary_prefix,
|
|
36
|
+
)
|
|
37
|
+
if mapping is None:
|
|
38
|
+
mapping = converters.encoders
|
|
39
|
+
return converters.escape_item(obj, charset, mapping=mapping)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def literal(
|
|
43
|
+
obj: Any,
|
|
44
|
+
charset: str = 'utf8',
|
|
45
|
+
encoders: Optional[Encoders] = None,
|
|
46
|
+
server_status: int = 0,
|
|
47
|
+
binary_prefix: bool = False,
|
|
48
|
+
) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Alias for escape().
|
|
51
|
+
|
|
52
|
+
Non-standard, for internal use; do not use this in your applications.
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
return escape(
|
|
56
|
+
obj, charset=charset, mapping=encoders,
|
|
57
|
+
server_status=server_status, binary_prefix=binary_prefix,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def escape_string(
|
|
62
|
+
s: str,
|
|
63
|
+
server_status: int = 0,
|
|
64
|
+
) -> str:
|
|
65
|
+
"""Escape a string value."""
|
|
66
|
+
if server_status & SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES:
|
|
67
|
+
return s.replace("'", "''")
|
|
68
|
+
return converters.escape_string(s)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _quote_bytes(
|
|
72
|
+
s: bytes,
|
|
73
|
+
server_status: int = 0,
|
|
74
|
+
binary_prefix: bool = False,
|
|
75
|
+
) -> str:
|
|
76
|
+
if server_status & SERVER_STATUS.SERVER_STATUS_NO_BACKSLASH_ESCAPES:
|
|
77
|
+
if binary_prefix:
|
|
78
|
+
return "_binary X'{}'".format(s.hex())
|
|
79
|
+
return "X'{}'".format(s.hex())
|
|
80
|
+
return converters.escape_bytes(s)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _escape_args(
|
|
84
|
+
args: Union[Sequence[Any], Dict[str, Any], None],
|
|
85
|
+
charset: str = 'utf8',
|
|
86
|
+
encoders: Optional[Encoders] = None,
|
|
87
|
+
server_status: int = 0,
|
|
88
|
+
binary_prefix: bool = False,
|
|
89
|
+
) -> Any:
|
|
90
|
+
if encoders is None:
|
|
91
|
+
encoders = converters.encoders
|
|
92
|
+
|
|
93
|
+
if isinstance(args, (tuple, list)):
|
|
94
|
+
return tuple(
|
|
95
|
+
literal(
|
|
96
|
+
arg, charset=charset, encoders=encoders,
|
|
97
|
+
server_status=server_status,
|
|
98
|
+
binary_prefix=binary_prefix,
|
|
99
|
+
) for arg in args
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
elif isinstance(args, dict):
|
|
103
|
+
return {
|
|
104
|
+
key: literal(
|
|
105
|
+
val, charset=charset, encoders=encoders,
|
|
106
|
+
server_status=server_status,
|
|
107
|
+
binary_prefix=binary_prefix,
|
|
108
|
+
) for (key, val) in args.items()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# If it's not a dictionary let's try escaping it anyways.
|
|
112
|
+
# Worst case it will throw a Value error
|
|
113
|
+
return escape(
|
|
114
|
+
args, charset=charset, mapping=encoders,
|
|
115
|
+
server_status=server_status, binary_prefix=binary_prefix,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def mogrify(
|
|
120
|
+
query: Union[str, bytes],
|
|
121
|
+
args: Union[Sequence[Any], Dict[str, Any], None] = None,
|
|
122
|
+
charset: str = 'utf8',
|
|
123
|
+
encoders: Optional[Encoders] = None,
|
|
124
|
+
server_status: int = 0,
|
|
125
|
+
binary_prefix: bool = False,
|
|
126
|
+
) -> Union[str, bytes]:
|
|
127
|
+
"""
|
|
128
|
+
Returns the exact string sent to the database by calling the execute() method.
|
|
129
|
+
|
|
130
|
+
This method follows the extension to the DB API 2.0 followed by Psycopg.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
query : str
|
|
135
|
+
Query to mogrify.
|
|
136
|
+
args : Sequence[Any] or Dict[str, Any] or Any, optional
|
|
137
|
+
Parameters used with query. (optional)
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
str : The query with argument binding applied.
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
if args:
|
|
145
|
+
query = query % _escape_args(
|
|
146
|
+
args, charset=charset,
|
|
147
|
+
encoders=encoders,
|
|
148
|
+
server_status=server_status,
|
|
149
|
+
binary_prefix=binary_prefix,
|
|
150
|
+
)
|
|
151
|
+
return query
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: singlestoredb
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Interface to the SingleStoreDB database and workspace management APIs
|
|
5
5
|
Home-page: https://github.com/singlestore-labs/singlestoredb-python
|
|
6
6
|
Author: SingleStore
|
|
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: PyJWT
|
|
18
18
|
Requires-Dist: build
|
|
19
|
+
Requires-Dist: parsimonious
|
|
19
20
|
Requires-Dist: requests
|
|
20
21
|
Requires-Dist: sqlparams
|
|
21
22
|
Requires-Dist: wheel
|