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
singlestoredb/tests/utils.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# type: ignore
|
|
3
3
|
"""Utilities for testing."""
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
4
|
import os
|
|
7
5
|
import uuid
|
|
8
6
|
from typing import Any
|
|
@@ -88,11 +86,12 @@ def load_sql(sql_file: str) -> str:
|
|
|
88
86
|
if cmd:
|
|
89
87
|
cmd += ';'
|
|
90
88
|
cur.execute(cmd)
|
|
91
|
-
|
|
89
|
+
|
|
90
|
+
elif not conn.driver.startswith('http'):
|
|
92
91
|
cur.execute(f'USE {dbname};')
|
|
93
92
|
|
|
94
93
|
# Start HTTP server as needed.
|
|
95
|
-
if http_port:
|
|
94
|
+
if http_port and not conn.driver.startswith('http'):
|
|
96
95
|
cur.execute(f'SET GLOBAL HTTP_PROXY_PORT={http_port};')
|
|
97
96
|
cur.execute('SET GLOBAL HTTP_API=ON;')
|
|
98
97
|
cur.execute('RESTART PROXY;')
|
singlestoredb/types.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB data type utilities."""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
3
|
import datetime
|
|
6
4
|
import decimal
|
|
7
5
|
import time
|
|
8
6
|
from typing import Dict
|
|
9
7
|
from typing import List
|
|
10
8
|
from typing import Set
|
|
9
|
+
from typing import Union
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
Date = datetime.date
|
|
@@ -78,8 +77,8 @@ class DBAPIType(object):
|
|
|
78
77
|
|
|
79
78
|
"""
|
|
80
79
|
|
|
81
|
-
def __init__(self, *values: int
|
|
82
|
-
self.values: Set[int
|
|
80
|
+
def __init__(self, *values: Union[int, str, type, 'DBAPIType']):
|
|
81
|
+
self.values: Set[Union[int, str, type]] = set()
|
|
83
82
|
name: str = ''
|
|
84
83
|
code: int = -1
|
|
85
84
|
for item in values:
|
|
@@ -135,7 +134,7 @@ class DBAPIType(object):
|
|
|
135
134
|
bool
|
|
136
135
|
|
|
137
136
|
"""
|
|
138
|
-
return not(self.__eq__(other))
|
|
137
|
+
return not (self.__eq__(other))
|
|
139
138
|
|
|
140
139
|
def __str__(self) -> str:
|
|
141
140
|
"""Return string representation."""
|
singlestoredb/utils/config.py
CHANGED
|
@@ -24,15 +24,17 @@ reset_option(...). The describe_option(...) function can be used to display
|
|
|
24
24
|
a description of one or more options.
|
|
25
25
|
|
|
26
26
|
"""
|
|
27
|
-
from __future__ import annotations
|
|
28
|
-
|
|
29
27
|
import contextlib
|
|
30
28
|
import os
|
|
31
29
|
import re
|
|
32
30
|
from typing import Any
|
|
33
31
|
from typing import Callable
|
|
32
|
+
from typing import Dict
|
|
34
33
|
from typing import Iterator
|
|
34
|
+
from typing import List
|
|
35
|
+
from typing import Mapping
|
|
35
36
|
from typing import Optional
|
|
37
|
+
from typing import Tuple
|
|
36
38
|
from typing import Union
|
|
37
39
|
from urllib.parse import urlparse
|
|
38
40
|
|
|
@@ -45,7 +47,7 @@ _config = xdict()
|
|
|
45
47
|
items_types = (list, tuple, set)
|
|
46
48
|
|
|
47
49
|
|
|
48
|
-
def _getenv(names: Union[str,
|
|
50
|
+
def _getenv(names: Union[str, List[str]], *args: Any) -> str:
|
|
49
51
|
"""
|
|
50
52
|
Check for multiple environment variable values.
|
|
51
53
|
|
|
@@ -79,7 +81,7 @@ def _getenv(names: Union[str, list[str]], *args: Any) -> str:
|
|
|
79
81
|
raise KeyError(names[0])
|
|
80
82
|
|
|
81
83
|
|
|
82
|
-
def _setenv(names: Union[str,
|
|
84
|
+
def _setenv(names: Union[str, List[str]], value: Any) -> None:
|
|
83
85
|
"""
|
|
84
86
|
Set environment variable.
|
|
85
87
|
|
|
@@ -109,7 +111,7 @@ def _setenv(names: Union[str, list[str]], value: Any) -> None:
|
|
|
109
111
|
os.environ[name] = value
|
|
110
112
|
|
|
111
113
|
|
|
112
|
-
def _delenv(names: Union[str,
|
|
114
|
+
def _delenv(names: Union[str, List[str]]) -> None:
|
|
113
115
|
"""Delete given environment variables."""
|
|
114
116
|
if not isinstance(names, items_types):
|
|
115
117
|
names = [names]
|
|
@@ -118,7 +120,7 @@ def _delenv(names: Union[str, list[str]]) -> None:
|
|
|
118
120
|
os.environ.pop(name.replace('_', ''), None)
|
|
119
121
|
|
|
120
122
|
|
|
121
|
-
def iteroptions(*args: Any, **kwargs: Any) -> Iterator[
|
|
123
|
+
def iteroptions(*args: Any, **kwargs: Any) -> Iterator[Tuple[str, Any]]:
|
|
122
124
|
"""
|
|
123
125
|
Iterate through name / value pairs of options
|
|
124
126
|
|
|
@@ -264,7 +266,7 @@ def get_option(key: str) -> Any:
|
|
|
264
266
|
return opt.get()
|
|
265
267
|
|
|
266
268
|
|
|
267
|
-
def get_suboptions(key: str) ->
|
|
269
|
+
def get_suboptions(key: str) -> Dict[str, Any]:
|
|
268
270
|
"""
|
|
269
271
|
Get the dictionary of options at the level `key`.
|
|
270
272
|
|
|
@@ -561,12 +563,34 @@ def check_bool(value: Union[bool, int]) -> bool:
|
|
|
561
563
|
raise ValueError('%s is not a recognized bool value')
|
|
562
564
|
|
|
563
565
|
|
|
566
|
+
def check_optional_bool(value: Optional[Union[bool, int]]) -> Optional[bool]:
|
|
567
|
+
"""
|
|
568
|
+
Validate an optional bool value.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
value : int or bool or None
|
|
573
|
+
The value to validate. If specified as an integer, it must
|
|
574
|
+
be either 0 for False or 1 for True.
|
|
575
|
+
|
|
576
|
+
Returns
|
|
577
|
+
-------
|
|
578
|
+
bool
|
|
579
|
+
The validated bool
|
|
580
|
+
|
|
581
|
+
"""
|
|
582
|
+
if value is None:
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
return check_bool(value)
|
|
586
|
+
|
|
587
|
+
|
|
564
588
|
def check_str(
|
|
565
589
|
value: Any,
|
|
566
590
|
pattern: Optional[str] = None,
|
|
567
591
|
max_length: Optional[int] = None,
|
|
568
592
|
min_length: Optional[int] = None,
|
|
569
|
-
valid_values: Optional[
|
|
593
|
+
valid_values: Optional[List[str]] = None,
|
|
570
594
|
) -> Optional[str]:
|
|
571
595
|
"""
|
|
572
596
|
Validate a string value.
|
|
@@ -622,12 +646,47 @@ def check_str(
|
|
|
622
646
|
return out
|
|
623
647
|
|
|
624
648
|
|
|
649
|
+
def check_dict_str_str(
|
|
650
|
+
value: Any,
|
|
651
|
+
) -> Optional[Dict[str, str]]:
|
|
652
|
+
"""
|
|
653
|
+
Validate a string value.
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
value : dict
|
|
658
|
+
The value to validate. Keys and values must be strings.
|
|
659
|
+
|
|
660
|
+
Returns
|
|
661
|
+
-------
|
|
662
|
+
dict
|
|
663
|
+
The validated dict value
|
|
664
|
+
"""
|
|
665
|
+
if value is None:
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
if not isinstance(value, Mapping):
|
|
669
|
+
raise ValueError(
|
|
670
|
+
'value {} must be of type dict'.format(value),
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
out = {}
|
|
674
|
+
for k, v in value.items():
|
|
675
|
+
if not isinstance(k, str) or not isinstance(v, str):
|
|
676
|
+
raise ValueError(
|
|
677
|
+
'keys and values in {} must be strings'.format(value),
|
|
678
|
+
)
|
|
679
|
+
out[k] = v
|
|
680
|
+
|
|
681
|
+
return out
|
|
682
|
+
|
|
683
|
+
|
|
625
684
|
def check_url(
|
|
626
685
|
value: str,
|
|
627
686
|
pattern: Optional[str] = None,
|
|
628
687
|
max_length: Optional[int] = None,
|
|
629
688
|
min_length: Optional[int] = None,
|
|
630
|
-
valid_values: Optional[
|
|
689
|
+
valid_values: Optional[List[str]] = None,
|
|
631
690
|
) -> Optional[str]:
|
|
632
691
|
"""
|
|
633
692
|
Validate a URL value.
|
|
@@ -696,7 +755,7 @@ class Option(object):
|
|
|
696
755
|
validator: Callable[[str], Any],
|
|
697
756
|
default: Any,
|
|
698
757
|
doc: str,
|
|
699
|
-
environ: Optional[Union[str,
|
|
758
|
+
environ: Optional[Union[str, List[str]]] = None,
|
|
700
759
|
):
|
|
701
760
|
self._name = name
|
|
702
761
|
self._typedesc = typedesc
|
|
@@ -776,7 +835,7 @@ def register_option(
|
|
|
776
835
|
validator: Callable[[Any], Any],
|
|
777
836
|
default: Any,
|
|
778
837
|
doc: str,
|
|
779
|
-
environ: Optional[Union[str,
|
|
838
|
+
environ: Optional[Union[str, List[str]]] = None,
|
|
780
839
|
) -> None:
|
|
781
840
|
"""
|
|
782
841
|
Register a new option.
|
|
@@ -819,7 +878,7 @@ class AttrOption(object):
|
|
|
819
878
|
def __init__(self, name: str):
|
|
820
879
|
object.__setattr__(self, '_name', name)
|
|
821
880
|
|
|
822
|
-
def __dir__(self) ->
|
|
881
|
+
def __dir__(self) -> List[str]:
|
|
823
882
|
"""Return list of flattened keys."""
|
|
824
883
|
if self._name in _config:
|
|
825
884
|
return _config[self._name].flatkeys()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from ..config import get_option
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def log_query(query: str, args: Any = None) -> None:
|
|
8
|
+
"""Log the query and parameters."""
|
|
9
|
+
if get_option('debug.queries'):
|
|
10
|
+
if args is None:
|
|
11
|
+
print('[QUERY]', query, file=sys.stderr)
|
|
12
|
+
else:
|
|
13
|
+
print('[QUERY]', query, args, file=sys.stderr)
|
|
@@ -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
|
singlestoredb/utils/results.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
"""SingleStoreDB package utilities."""
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
3
|
import collections
|
|
6
4
|
import warnings
|
|
7
5
|
from typing import Any
|
|
@@ -30,7 +28,7 @@ class Description(NamedTuple):
|
|
|
30
28
|
"""Column definition."""
|
|
31
29
|
|
|
32
30
|
name: str
|
|
33
|
-
type_code:
|
|
31
|
+
type_code: int
|
|
34
32
|
display_size: Optional[int]
|
|
35
33
|
internal_size: Optional[int]
|
|
36
34
|
precision: Optional[int]
|
|
@@ -192,8 +190,11 @@ _converters: Dict[
|
|
|
192
190
|
],
|
|
193
191
|
] = {
|
|
194
192
|
'tuple': results_to_tuple,
|
|
193
|
+
'tuples': results_to_tuple,
|
|
195
194
|
'namedtuple': results_to_namedtuple,
|
|
195
|
+
'namedtuples': results_to_namedtuple,
|
|
196
196
|
'dict': results_to_dict,
|
|
197
|
+
'dicts': results_to_dict,
|
|
197
198
|
'dataframe': results_to_dataframe,
|
|
198
199
|
}
|
|
199
200
|
|
singlestoredb/utils/xdict.py
CHANGED
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
# This file originally copied from https://github.com/sassoftware/python-swat
|
|
18
18
|
#
|
|
19
19
|
"""Dictionary that allows setting nested keys by period (.) delimited strings."""
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
20
|
import copy
|
|
23
21
|
import re
|
|
24
22
|
from typing import Any
|
|
@@ -26,6 +24,8 @@ from typing import Dict
|
|
|
26
24
|
from typing import ItemsView
|
|
27
25
|
from typing import Iterable
|
|
28
26
|
from typing import KeysView
|
|
27
|
+
from typing import List
|
|
28
|
+
from typing import Tuple
|
|
29
29
|
from typing import ValuesView
|
|
30
30
|
|
|
31
31
|
|
|
@@ -74,7 +74,7 @@ class xdict(Dict[str, Any]):
|
|
|
74
74
|
|
|
75
75
|
"""
|
|
76
76
|
|
|
77
|
-
_dir:
|
|
77
|
+
_dir: List[str]
|
|
78
78
|
|
|
79
79
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
80
80
|
super(xdict, self).__init__()
|
|
@@ -86,7 +86,7 @@ class xdict(Dict[str, Any]):
|
|
|
86
86
|
return list(self._dir)
|
|
87
87
|
return super(xdict, self).__dir__()
|
|
88
88
|
|
|
89
|
-
def set_dir_values(self, values:
|
|
89
|
+
def set_dir_values(self, values: List[str]) -> None:
|
|
90
90
|
"""
|
|
91
91
|
Set the valid values for keys to display in tab-completion.
|
|
92
92
|
|
|
@@ -102,11 +102,11 @@ class xdict(Dict[str, Any]):
|
|
|
102
102
|
"""Set the docstring for the xdict."""
|
|
103
103
|
super(xdict, self).__setattr__('__doc__', docstring)
|
|
104
104
|
|
|
105
|
-
def __copy__(self) -> xdict:
|
|
105
|
+
def __copy__(self) -> 'xdict':
|
|
106
106
|
"""Return a copy of self."""
|
|
107
107
|
return type(self)(**self)
|
|
108
108
|
|
|
109
|
-
def __deepcopy__(self, memo: Any) -> xdict:
|
|
109
|
+
def __deepcopy__(self, memo: Any) -> 'xdict':
|
|
110
110
|
"""Return a deep copy of self."""
|
|
111
111
|
out = type(self)()
|
|
112
112
|
for key, value in self.items():
|
|
@@ -116,7 +116,7 @@ class xdict(Dict[str, Any]):
|
|
|
116
116
|
return out
|
|
117
117
|
|
|
118
118
|
@classmethod
|
|
119
|
-
def from_json(cls, jsonstr: str) -> xdict:
|
|
119
|
+
def from_json(cls, jsonstr: str) -> 'xdict':
|
|
120
120
|
"""
|
|
121
121
|
Create an xdict object from a JSON string.
|
|
122
122
|
|
|
@@ -309,7 +309,7 @@ class xdict(Dict[str, Any]):
|
|
|
309
309
|
self._flatten(self, output)
|
|
310
310
|
return output
|
|
311
311
|
|
|
312
|
-
def allkeys(self) ->
|
|
312
|
+
def allkeys(self) -> List[str]:
|
|
313
313
|
"""Return a list of all possible keys (even sub-keys) in the xdict."""
|
|
314
314
|
out = set()
|
|
315
315
|
for key in self.flatkeys():
|
|
@@ -321,15 +321,15 @@ class xdict(Dict[str, Any]):
|
|
|
321
321
|
out.add(re.sub(r'\[\d+\]', r'', key))
|
|
322
322
|
return list(out)
|
|
323
323
|
|
|
324
|
-
def flatkeys(self) ->
|
|
324
|
+
def flatkeys(self) -> List[str]:
|
|
325
325
|
"""Return a list of flattened keys in the xdict."""
|
|
326
326
|
return list(self.flattened().keys())
|
|
327
327
|
|
|
328
|
-
def flatvalues(self) ->
|
|
328
|
+
def flatvalues(self) -> List[Any]:
|
|
329
329
|
"""Return a list of flattened values in the xdict."""
|
|
330
330
|
return list(self.flattened().values())
|
|
331
331
|
|
|
332
|
-
def flatitems(self) ->
|
|
332
|
+
def flatitems(self) -> List[Tuple[str, Any]]:
|
|
333
333
|
"""Return tuples of flattened key/value pairs."""
|
|
334
334
|
return list(self.flattened().items())
|
|
335
335
|
|
|
@@ -341,7 +341,7 @@ class xdict(Dict[str, Any]):
|
|
|
341
341
|
"""Return iterator of flattened values."""
|
|
342
342
|
return iter(self.flattened().values())
|
|
343
343
|
|
|
344
|
-
def iterflatitems(self) -> Iterable[
|
|
344
|
+
def iterflatitems(self) -> Iterable[Tuple[str, Any]]:
|
|
345
345
|
"""Return iterator of flattened items."""
|
|
346
346
|
return iter(self.flattened().items())
|
|
347
347
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: singlestoredb
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Interface to the SingleStoreDB database and workspace management APIs
|
|
5
|
+
Home-page: https://github.com/singlestore-labs/singlestoredb-python
|
|
6
|
+
Author: SingleStore
|
|
7
|
+
Author-email: support@singlestore.com
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Topic :: Database
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: PyJWT
|
|
18
|
+
Requires-Dist: build
|
|
19
|
+
Requires-Dist: parsimonious
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: setuptools
|
|
22
|
+
Requires-Dist: sqlparams
|
|
23
|
+
Requires-Dist: wheel
|
|
24
|
+
Provides-Extra: dataframe
|
|
25
|
+
Requires-Dist: ibis-singlestoredb ; extra == 'dataframe'
|
|
26
|
+
Provides-Extra: dbt
|
|
27
|
+
Requires-Dist: dbt-singlestore ; extra == 'dbt'
|
|
28
|
+
Provides-Extra: ed22519
|
|
29
|
+
Requires-Dist: PyNaCl >=1.4.0 ; extra == 'ed22519'
|
|
30
|
+
Provides-Extra: gssapi
|
|
31
|
+
Requires-Dist: gssapi ; extra == 'gssapi'
|
|
32
|
+
Provides-Extra: ibis
|
|
33
|
+
Requires-Dist: ibis-singlestoredb ; extra == 'ibis'
|
|
34
|
+
Provides-Extra: kerberos
|
|
35
|
+
Requires-Dist: gssapi ; extra == 'kerberos'
|
|
36
|
+
Provides-Extra: pytest
|
|
37
|
+
Requires-Dist: pytest ; extra == 'pytest'
|
|
38
|
+
Provides-Extra: rsa
|
|
39
|
+
Requires-Dist: cryptography ; extra == 'rsa'
|
|
40
|
+
Provides-Extra: sqlalchemy
|
|
41
|
+
Requires-Dist: sqlalchemy-singlestoredb >=1.0.0 ; extra == 'sqlalchemy'
|
|
42
|
+
|
|
43
|
+
# <img src="https://github.com/singlestore-labs/singlestoredb-python/blob/main/resources/singlestore-logo.png" height="60" valign="middle"/> SingleStoreDB Python SDK
|
|
44
|
+
|
|
45
|
+
This project contains a [DB-API 2.0](https://www.python.org/dev/peps/pep-0249/)
|
|
46
|
+
compatible Python interface to the SingleStore database and workspace management API.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
This package can be install from PyPI using `pip`:
|
|
51
|
+
```
|
|
52
|
+
pip install singlestoredb
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
https://singlestore-labs.github.io/singlestoredb-python
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
Connections to the SingleStore database are made using the DB-API parameters
|
|
62
|
+
`host`, `port`, `user`, `password`, etc, but they may also be done using
|
|
63
|
+
URLs that specify these parameters as well (much like the
|
|
64
|
+
[SQLAlchemy](https://www.sqlalchemy.org) package).
|
|
65
|
+
```
|
|
66
|
+
import singlestoredb as s2
|
|
67
|
+
|
|
68
|
+
# Connect using the default connector
|
|
69
|
+
conn = s2.connect('user:password@host:3306/db_name')
|
|
70
|
+
|
|
71
|
+
# Create a cursor
|
|
72
|
+
cur = conn.cursor()
|
|
73
|
+
|
|
74
|
+
# Execute SQL
|
|
75
|
+
cur.execute('select * from foo')
|
|
76
|
+
|
|
77
|
+
# Fetch the results
|
|
78
|
+
print(cur.description)
|
|
79
|
+
for item in cur:
|
|
80
|
+
print(item)
|
|
81
|
+
|
|
82
|
+
# Close the connection
|
|
83
|
+
conn.close()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Connecting to the HTTP API is done as follows:
|
|
87
|
+
```
|
|
88
|
+
# Use the HTTP API connector
|
|
89
|
+
conn = s2.connect('https://user:password@host:8080/db_name')
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Performance
|
|
93
|
+
|
|
94
|
+
While this package is based on [PyMySQL](https://github.com/PyMySQL/PyMySQL)
|
|
95
|
+
which is a pure Python-based MySQL connector, it adds various performance
|
|
96
|
+
enhancements that make it faster than most other connectors. The performance
|
|
97
|
+
improvements come from changes to the data conversion functions, cursor implementations,
|
|
98
|
+
and a C extension that is highly optimized to improve row data reading.
|
|
99
|
+
|
|
100
|
+
The package can be used both in a pure Python mode and as well as a C accelerated
|
|
101
|
+
mode. Generally speaking, the C accelerated version of the client can read
|
|
102
|
+
data 10X faster than PyMySQL, 2X faster than MySQLdb, and 1.5X faster than
|
|
103
|
+
mysql.connector. All of this is done without having to install any 3rd party
|
|
104
|
+
MySQL libraries!
|
|
105
|
+
|
|
106
|
+
Benchmarking was done with a table of 3,533,286 rows each containing a datetime,
|
|
107
|
+
a float, and eight character columns. The data is the same data set used in
|
|
108
|
+
[this article](https://www.singlestore.com/blog/how-to-get-started-with-singlestore/).
|
|
109
|
+
The client and server were running on the same machine and queries were made
|
|
110
|
+
using `fetchone`, `fetchall`, `fetchmany(1000)`, and an iterator over the cursor
|
|
111
|
+
object (e.g., `iter(cur)`). The results are shown below.
|
|
112
|
+
|
|
113
|
+
### Buffered
|
|
114
|
+
|
|
115
|
+
| | PyMySQL | MySQLdb | mysql.connector | SingleStore (pure Python) | SingleStore |
|
|
116
|
+
|-------------------------|---------|---------|-----------------|---------------------------|-------------|
|
|
117
|
+
| fetchall | 37.0s | 8.7s | 5.6s | 29.0s | 3.7s |
|
|
118
|
+
| fetchmany(1000) | 37.4s | 9.2s | 6.2s | 29.6s | 3.6s |
|
|
119
|
+
| fetchone | 38.2s | 10.1s | 10.2s | 30.9s | 4.8s |
|
|
120
|
+
| iter(cur) | 38.3s | 9.1s | 10.2s | 30.4s | 4.4s |
|
|
121
|
+
|
|
122
|
+
### Unbuffered
|
|
123
|
+
|
|
124
|
+
| | PyMySQL | MySQLdb | mysql.connector | SingleStore (pure Python) | SingleStore |
|
|
125
|
+
|-------------------------|---------|---------|-----------------|---------------------------|-------------|
|
|
126
|
+
| fetchall | 39.0s | 6.5s | 5.5s | 30.3s | 5.5s |
|
|
127
|
+
| fetchmany(1000) | 39.4s | 7.0s | 6.0s | 30.4s | 4.1s |
|
|
128
|
+
| fetchone | 34.5s | 8.9s | 10.1s | 30.8s | 6.6s |
|
|
129
|
+
| iter(cur) | 39.0s | 9.0s | 10.2s | 31.4s | 6.0s |
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
This library is licensed under the [Apache 2.0 License](https://raw.githubusercontent.com/singlestore-labs/singlestoredb-python/main/LICENSE?token=GHSAT0AAAAAABMGV6QPNR6N23BVICDYK5LAYTVK5EA).
|
|
135
|
+
|
|
136
|
+
## Resources
|
|
137
|
+
|
|
138
|
+
* [SingleStore](https://singlestore.com)
|
|
139
|
+
* [Python](https://python.org)
|