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.

Files changed (121) hide show
  1. singlestoredb/__init__.py +33 -2
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +6 -4
  4. singlestoredb/config.py +116 -16
  5. singlestoredb/connection.py +489 -523
  6. singlestoredb/converters.py +275 -26
  7. singlestoredb/exceptions.py +30 -4
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/__init__.py +0 -0
  22. singlestoredb/fusion/handlers/stage.py +257 -0
  23. singlestoredb/fusion/handlers/utils.py +162 -0
  24. singlestoredb/fusion/handlers/workspace.py +412 -0
  25. singlestoredb/fusion/registry.py +164 -0
  26. singlestoredb/fusion/result.py +399 -0
  27. singlestoredb/http/__init__.py +27 -0
  28. singlestoredb/http/connection.py +1192 -0
  29. singlestoredb/management/__init__.py +3 -2
  30. singlestoredb/management/billing_usage.py +148 -0
  31. singlestoredb/management/cluster.py +19 -14
  32. singlestoredb/management/manager.py +100 -40
  33. singlestoredb/management/organization.py +188 -0
  34. singlestoredb/management/region.py +6 -8
  35. singlestoredb/management/utils.py +253 -4
  36. singlestoredb/management/workspace.py +1153 -35
  37. singlestoredb/mysql/__init__.py +177 -0
  38. singlestoredb/mysql/_auth.py +298 -0
  39. singlestoredb/mysql/charset.py +214 -0
  40. singlestoredb/mysql/connection.py +1814 -0
  41. singlestoredb/mysql/constants/CLIENT.py +38 -0
  42. singlestoredb/mysql/constants/COMMAND.py +32 -0
  43. singlestoredb/mysql/constants/CR.py +78 -0
  44. singlestoredb/mysql/constants/ER.py +474 -0
  45. singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
  46. singlestoredb/mysql/constants/FLAG.py +15 -0
  47. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  48. singlestoredb/mysql/constants/__init__.py +0 -0
  49. singlestoredb/mysql/converters.py +271 -0
  50. singlestoredb/mysql/cursors.py +713 -0
  51. singlestoredb/mysql/err.py +92 -0
  52. singlestoredb/mysql/optionfile.py +20 -0
  53. singlestoredb/mysql/protocol.py +388 -0
  54. singlestoredb/mysql/tests/__init__.py +19 -0
  55. singlestoredb/mysql/tests/base.py +126 -0
  56. singlestoredb/mysql/tests/conftest.py +37 -0
  57. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  58. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  59. singlestoredb/mysql/tests/test_basic.py +452 -0
  60. singlestoredb/mysql/tests/test_connection.py +851 -0
  61. singlestoredb/mysql/tests/test_converters.py +58 -0
  62. singlestoredb/mysql/tests/test_cursor.py +141 -0
  63. singlestoredb/mysql/tests/test_err.py +16 -0
  64. singlestoredb/mysql/tests/test_issues.py +514 -0
  65. singlestoredb/mysql/tests/test_load_local.py +75 -0
  66. singlestoredb/mysql/tests/test_nextset.py +88 -0
  67. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  68. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  69. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  70. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  71. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  72. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  73. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  74. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  75. singlestoredb/mysql/times.py +23 -0
  76. singlestoredb/pytest.py +283 -0
  77. singlestoredb/tests/empty.sql +0 -0
  78. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  79. singlestoredb/tests/test.sql +210 -0
  80. singlestoredb/tests/test2.sql +1 -0
  81. singlestoredb/tests/test_basics.py +482 -117
  82. singlestoredb/tests/test_config.py +13 -15
  83. singlestoredb/tests/test_connection.py +241 -289
  84. singlestoredb/tests/test_dbapi.py +27 -0
  85. singlestoredb/tests/test_exceptions.py +0 -2
  86. singlestoredb/tests/test_ext_func.py +1193 -0
  87. singlestoredb/tests/test_ext_func_data.py +1101 -0
  88. singlestoredb/tests/test_fusion.py +465 -0
  89. singlestoredb/tests/test_http.py +32 -28
  90. singlestoredb/tests/test_management.py +588 -10
  91. singlestoredb/tests/test_plugin.py +33 -0
  92. singlestoredb/tests/test_results.py +11 -14
  93. singlestoredb/tests/test_types.py +0 -2
  94. singlestoredb/tests/test_udf.py +687 -0
  95. singlestoredb/tests/test_xdict.py +0 -2
  96. singlestoredb/tests/utils.py +3 -4
  97. singlestoredb/types.py +4 -5
  98. singlestoredb/utils/config.py +71 -12
  99. singlestoredb/utils/convert_rows.py +0 -2
  100. singlestoredb/utils/debug.py +13 -0
  101. singlestoredb/utils/mogrify.py +151 -0
  102. singlestoredb/utils/results.py +4 -3
  103. singlestoredb/utils/xdict.py +12 -12
  104. singlestoredb-1.0.3.dist-info/METADATA +139 -0
  105. singlestoredb-1.0.3.dist-info/RECORD +112 -0
  106. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
  107. singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
  108. singlestoredb/drivers/__init__.py +0 -46
  109. singlestoredb/drivers/base.py +0 -200
  110. singlestoredb/drivers/cymysql.py +0 -40
  111. singlestoredb/drivers/http.py +0 -49
  112. singlestoredb/drivers/mariadb.py +0 -42
  113. singlestoredb/drivers/mysqlconnector.py +0 -51
  114. singlestoredb/drivers/mysqldb.py +0 -62
  115. singlestoredb/drivers/pymysql.py +0 -39
  116. singlestoredb/drivers/pyodbc.py +0 -67
  117. singlestoredb/http.py +0 -794
  118. singlestoredb-0.3.3.dist-info/METADATA +0 -105
  119. singlestoredb-0.3.3.dist-info/RECORD +0 -46
  120. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
  121. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env python
2
2
  """SingleStoreDB Cluster Management."""
3
- from __future__ import annotations
4
-
5
3
  from typing import Dict
6
4
  from typing import Optional
7
5
 
@@ -14,16 +12,16 @@ class Region(object):
14
12
  Cluster region information.
15
13
 
16
14
  This object is not directly instantiated. It is used in results
17
- of `ClusterManager` API calls.
15
+ of ``WorkspaceManager`` API calls.
18
16
 
19
17
  See Also
20
18
  --------
21
- :attr:`ClusterManager.regions`
19
+ :attr:`WorkspaceManager.regions`
22
20
 
23
21
  """
24
22
 
25
23
  def __init__(self, id: str, name: str, provider: str):
26
- """Use :attr:`ClusterManager.regions` instead."""
24
+ """Use :attr:`WorkspaceManager.regions` instead."""
27
25
  #: Unique ID of the region
28
26
  self.id = id
29
27
 
@@ -44,7 +42,7 @@ class Region(object):
44
42
  return str(self)
45
43
 
46
44
  @classmethod
47
- def from_dict(cls, obj: Dict[str, str], manager: Manager) -> Region:
45
+ def from_dict(cls, obj: Dict[str, str], manager: Manager) -> 'Region':
48
46
  """
49
47
  Convert dictionary to a ``Region`` object.
50
48
 
@@ -52,8 +50,8 @@ class Region(object):
52
50
  ----------
53
51
  obj : dict
54
52
  Key-value pairs to retrieve region information from
55
- manager : ClusterManager, optional
56
- The ClusterManager the Region belongs to
53
+ manager : WorkspaceManager, optional
54
+ The WorkspaceManager the Region belongs to
57
55
 
58
56
  Returns
59
57
  -------
@@ -1,11 +1,166 @@
1
1
  #!/usr/bin/env python
2
2
  """SingleStoreDB Cluster Management."""
3
- from __future__ import annotations
4
-
5
3
  import datetime
4
+ import functools
5
+ import os
6
+ import re
7
+ import sys
6
8
  from typing import Any
9
+ from typing import Callable
10
+ from typing import Dict
11
+ from typing import List
7
12
  from typing import Optional
13
+ from typing import SupportsIndex
14
+ from typing import TypeVar
8
15
  from typing import Union
16
+ from urllib.parse import urlparse
17
+
18
+ import jwt
19
+
20
+ from .. import converters
21
+ from ..config import get_option
22
+
23
+ JSON = Union[str, List[str], Dict[str, 'JSON']]
24
+ JSONObj = Dict[str, JSON]
25
+ JSONList = List[JSON]
26
+ T = TypeVar('T')
27
+
28
+ if sys.version_info < (3, 10):
29
+ PathLike = Union[str, os.PathLike]
30
+ PathLikeABC = os.PathLike
31
+ else:
32
+ PathLike = Union[str, os.PathLike[str]]
33
+ PathLikeABC = os.PathLike[str]
34
+
35
+
36
+ class TTLProperty(object):
37
+ """Property with time limit."""
38
+
39
+ def __init__(self, fget: Callable[[Any], Any], ttl: datetime.timedelta):
40
+ self.fget = fget
41
+ self.ttl = ttl
42
+ self._last_executed = datetime.datetime(2000, 1, 1)
43
+ self._last_result = None
44
+ self.__doc__ = fget.__doc__
45
+ self._name = ''
46
+
47
+ def reset(self) -> None:
48
+ self._last_executed = datetime.datetime(2000, 1, 1)
49
+ self._last_result = None
50
+
51
+ def __set_name__(self, owner: Any, name: str) -> None:
52
+ self._name = name
53
+
54
+ def __get__(self, obj: Any, objtype: Any = None) -> Any:
55
+ if obj is None:
56
+ return self
57
+
58
+ if self._last_result is not None \
59
+ and (datetime.datetime.now() - self._last_executed) < self.ttl:
60
+ return self._last_result
61
+
62
+ self._last_result = self.fget(obj)
63
+ self._last_executed = datetime.datetime.now()
64
+
65
+ return self._last_result
66
+
67
+
68
+ def ttl_property(ttl: datetime.timedelta) -> Callable[[Any], Any]:
69
+ """Property with a time-to-live."""
70
+ def wrapper(func: Callable[[Any], Any]) -> Any:
71
+ out = TTLProperty(func, ttl=ttl)
72
+ return functools.wraps(func)(out)
73
+ return wrapper
74
+
75
+
76
+ class NamedList(List[T]):
77
+ """List class which also allows selection by ``name`` and ``id`` attribute."""
78
+
79
+ def _find_item(self, key: str) -> T:
80
+ for item in self:
81
+ if getattr(item, 'name', '') == key:
82
+ return item
83
+ if getattr(item, 'id', '') == key:
84
+ return item
85
+ raise KeyError(key)
86
+
87
+ def __getitem__(self, key: Union[SupportsIndex, slice, str]) -> Any:
88
+ if isinstance(key, str):
89
+ return self._find_item(key)
90
+ return super().__getitem__(key)
91
+
92
+ def __contains__(self, key: Any) -> bool:
93
+ if isinstance(key, str):
94
+ try:
95
+ self._find_item(key)
96
+ return True
97
+ except KeyError:
98
+ return False
99
+ return super().__contains__(key)
100
+
101
+ def names(self) -> List[str]:
102
+ """Return ``name`` attribute of each item."""
103
+ return [y for y in [getattr(x, 'name', None) for x in self] if y is not None]
104
+
105
+ def ids(self) -> List[str]:
106
+ """Return ``id`` attribute of each item."""
107
+ return [y for y in [getattr(x, 'id', None) for x in self] if y is not None]
108
+
109
+ def get(self, name_or_id: str, *default: Any) -> Any:
110
+ """Return object with name / ID if it exists, or return default value."""
111
+ try:
112
+ return self._find_item(name_or_id)
113
+ except KeyError:
114
+ if default:
115
+ return default[0]
116
+ raise
117
+
118
+
119
+ def get_token() -> Optional[str]:
120
+ """Return the token for the Management API."""
121
+ # See if an API key is configured
122
+ tok = get_option('management.token')
123
+ if tok:
124
+ return tok
125
+
126
+ url = os.environ.get('SINGLESTOREDB_URL')
127
+ if not url:
128
+ # See if the connection URL contains a JWT
129
+ url = get_option('host')
130
+ if not url:
131
+ return None
132
+
133
+ urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
134
+ if urlp.password:
135
+ try:
136
+ jwt.decode(urlp.password, options={'verify_signature': False})
137
+ return urlp.password
138
+ except jwt.DecodeError:
139
+ pass
140
+
141
+ # Didn't find a key anywhere
142
+ return None
143
+
144
+
145
+ def get_organization() -> Optional[str]:
146
+ """Return the organization for the current token or environment."""
147
+ org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
148
+ if org:
149
+ return org
150
+
151
+ return None
152
+
153
+
154
+ def enable_http_tracing() -> None:
155
+ """Enable tracing of HTTP requests."""
156
+ import logging
157
+ import http.client as http_client
158
+ http_client.HTTPConnection.debuglevel = 1
159
+ logging.basicConfig()
160
+ logging.getLogger().setLevel(logging.DEBUG)
161
+ requests_log = logging.getLogger('requests.packages.urllib3')
162
+ requests_log.setLevel(logging.DEBUG)
163
+ requests_log.propagate = True
9
164
 
10
165
 
11
166
  def to_datetime(
@@ -16,20 +171,114 @@ def to_datetime(
16
171
  return None
17
172
  if isinstance(obj, datetime.datetime):
18
173
  return obj
174
+ if obj == '0001-01-01T00:00:00Z':
175
+ return None
19
176
  obj = obj.replace('Z', '')
20
177
  # Fix datetimes with truncated zeros
21
178
  if '.' in obj:
22
179
  obj, micros = obj.split('.', 1)
23
180
  micros = micros + '0' * (6 - len(micros))
24
181
  obj = obj + '.' + micros
25
- return datetime.datetime.fromisoformat(obj)
182
+ out = converters.datetime_fromisoformat(obj)
183
+ if isinstance(out, str):
184
+ return None
185
+ if isinstance(out, datetime.date):
186
+ return datetime.datetime(out.year, out.month, out.day)
187
+ return out
188
+
189
+
190
+ def from_datetime(
191
+ obj: Union[str, datetime.datetime],
192
+ ) -> Optional[str]:
193
+ """Convert datetime to string."""
194
+ if not obj:
195
+ return None
196
+ if isinstance(obj, str):
197
+ return obj
198
+ out = obj.isoformat()
199
+ if not re.search(r'[A-Za-z]$', out):
200
+ out = f'{out}Z'
201
+ return out
26
202
 
27
203
 
28
204
  def vars_to_str(obj: Any) -> str:
29
205
  """Render a string representation of vars(obj)."""
30
206
  attrs = []
31
- for name, value in sorted(vars(obj).items()):
207
+ obj_vars = vars(obj)
208
+ if 'name' in obj_vars:
209
+ attrs.append('name={}'.format(repr(obj_vars['name'])))
210
+ if 'id' in obj_vars:
211
+ attrs.append('id={}'.format(repr(obj_vars['id'])))
212
+ for name, value in sorted(obj_vars.items()):
213
+ if name in ('name', 'id'):
214
+ continue
32
215
  if not value or name.startswith('_'):
33
216
  continue
34
217
  attrs.append('{}={}'.format(name, repr(value)))
35
218
  return '{}({})'.format(type(obj).__name__, ', '.join(attrs))
219
+
220
+
221
+ def single_item(s: Any) -> Any:
222
+ """Return only item if ``s`` is a list, otherwise return ``s``."""
223
+ if isinstance(s, list):
224
+ if len(s) != 1:
225
+ raise ValueError('list must only contain a singleitem')
226
+ return s[0]
227
+ return s
228
+
229
+
230
+ def stringify(s: JSON) -> str:
231
+ """Convert list of strings to single string."""
232
+ if isinstance(s, (tuple, list)):
233
+ if len(s) > 1:
234
+ raise ValueError('list contains more than one item')
235
+ return s[0]
236
+ if isinstance(s, dict):
237
+ raise TypeError('only strings and lists are valid arguments')
238
+ return s
239
+
240
+
241
+ def listify(s: JSON) -> List[str]:
242
+ """Convert string to list of strings."""
243
+ if isinstance(s, (tuple, list)):
244
+ return list(s)
245
+ if isinstance(s, dict):
246
+ raise TypeError('only strings and lists are valid arguments')
247
+ return [s]
248
+
249
+
250
+ def listify_obj(s: JSON) -> List[JSONObj]:
251
+ """Convert object to list of objects."""
252
+ if isinstance(s, (tuple, list)):
253
+ for item in s:
254
+ if not isinstance(item, dict):
255
+ raise TypeError('only dicts and lists of dicts are valid parameters')
256
+ return list(s) # type: ignore
257
+ if not isinstance(s, dict):
258
+ raise TypeError('only dicts and lists of dicts are valid parameters')
259
+ return [s]
260
+
261
+
262
+ def _upper_match(m: Any) -> str:
263
+ """Upper-case the first match group."""
264
+ return m.group(1).upper()
265
+
266
+
267
+ def snake_to_camel(s: Optional[str], cap_first: bool = False) -> Optional[str]:
268
+ """Convert snake-case to camel-case."""
269
+ if s is None:
270
+ return None
271
+ out = re.sub(r'_[A-Za-z]', _upper_match, s.lower())
272
+ if cap_first and out:
273
+ return out[0].upper() + out[1:]
274
+ return out
275
+
276
+
277
+ def camel_to_snake(s: Optional[str]) -> Optional[str]:
278
+ """Convert camel-case to snake-case."""
279
+ if s is None:
280
+ return None
281
+ out = re.sub(r'([A-Z]+)', r'_\1', s).lower()
282
+ if out and out[0] == '_':
283
+ return out[1:]
284
+ return out