singlestoredb 1.12.1__cp38-abi3-win_amd64.whl → 1.12.3__cp38-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 CHANGED
Binary file
singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.12.1'
16
+ __version__ = '1.12.3'
17
17
 
18
18
  from typing import Any
19
19
 
singlestoredb/config.py CHANGED
@@ -80,7 +80,7 @@ register_option(
80
80
  )
81
81
 
82
82
  register_option(
83
- 'charset', 'string', check_str, 'utf8',
83
+ 'charset', 'string', check_str, 'utf8mb4',
84
84
  'Specifies the character set for the session.',
85
85
  environ='SINGLESTOREDB_CHARSET',
86
86
  )
@@ -69,6 +69,12 @@ try:
69
69
  except ImportError:
70
70
  has_cloudpickle = False
71
71
 
72
+ try:
73
+ from pydantic import BaseModel
74
+ has_pydantic = True
75
+ except ImportError:
76
+ has_pydantic = False
77
+
72
78
 
73
79
  logger = utils.get_logger('singlestoredb.functions.ext.asgi')
74
80
 
@@ -138,13 +144,24 @@ def get_func_names(funcs: str) -> List[Tuple[str, str]]:
138
144
 
139
145
 
140
146
  def as_tuple(x: Any) -> Any:
141
- if hasattr(x, 'model_fields'):
142
- return tuple(x.model_fields.values())
147
+ """Convert object to tuple."""
148
+ if has_pydantic and isinstance(x, BaseModel):
149
+ return tuple(x.model_dump().values())
143
150
  if dataclasses.is_dataclass(x):
144
151
  return dataclasses.astuple(x)
145
152
  return x
146
153
 
147
154
 
155
+ def as_list_of_tuples(x: Any) -> Any:
156
+ """Convert object to a list of tuples."""
157
+ if isinstance(x, (list, tuple)) and len(x) > 0:
158
+ if has_pydantic and isinstance(x[0], BaseModel):
159
+ return [tuple(y.model_dump().values()) for y in x]
160
+ if dataclasses.is_dataclass(x[0]):
161
+ return [dataclasses.astuple(y) for y in x]
162
+ return x
163
+
164
+
148
165
  def make_func(
149
166
  name: str,
150
167
  func: Callable[..., Any],
@@ -183,7 +200,7 @@ def make_func(
183
200
  out_ids: List[int] = []
184
201
  out = []
185
202
  for i, res in zip(row_ids, func_map(func, rows)):
186
- out.extend(as_tuple(res))
203
+ out.extend(as_list_of_tuples(res))
187
204
  out_ids.extend([row_ids[i]] * (len(out)-len(out_ids)))
188
205
  return out_ids, out
189
206
 
@@ -6,6 +6,8 @@ import numbers
6
6
  import os
7
7
  import re
8
8
  import string
9
+ import sys
10
+ import types
9
11
  import typing
10
12
  from typing import Any
11
13
  from typing import Callable
@@ -32,6 +34,11 @@ except ImportError:
32
34
  from . import dtypes as dt
33
35
  from ..mysql.converters import escape_item # type: ignore
34
36
 
37
+ if sys.version_info >= (3, 10):
38
+ _UNION_TYPES = {typing.Union, types.UnionType}
39
+ else:
40
+ _UNION_TYPES = {typing.Union}
41
+
35
42
 
36
43
  array_types: Tuple[Any, ...]
37
44
 
@@ -211,7 +218,7 @@ def simplify_dtype(dtype: Any) -> List[Any]:
211
218
  args = []
212
219
 
213
220
  # Flatten Unions
214
- if origin is Union:
221
+ if origin in _UNION_TYPES:
215
222
  for x in typing.get_args(dtype):
216
223
  args.extend(simplify_dtype(x))
217
224
 
@@ -199,59 +199,65 @@ def get_deployment(
199
199
  """
200
200
  manager = get_workspace_manager()
201
201
 
202
+ #
203
+ # Search for deployment by name
204
+ #
202
205
  deployment_name = params.get('deployment_name') or \
203
206
  (params.get('in_deployment') or {}).get('deployment_name') or \
204
207
  (params.get('group') or {}).get('deployment_name') or \
205
208
  ((params.get('in') or {}).get('in_group') or {}).get('deployment_name') or \
206
209
  ((params.get('in') or {}).get('in_deployment') or {}).get('deployment_name')
210
+
207
211
  if deployment_name:
212
+ # Standard workspace group
208
213
  workspace_groups = [
209
214
  x for x in manager.workspace_groups
210
215
  if x.name == deployment_name
211
216
  ]
212
217
 
213
- starter_workspaces = []
214
- if not workspace_groups:
215
- filtered_starter_workspaces = [
216
- x for x in manager.starter_workspaces
217
- if x.name == deployment_name
218
- ]
219
-
220
- if not filtered_starter_workspaces:
221
- raise KeyError(
222
- f'no deployment found with name: {deployment_name}',
223
- )
224
-
225
- starter_workspaces = filtered_starter_workspaces
218
+ if len(workspace_groups) == 1:
219
+ return workspace_groups[0]
226
220
 
227
- if len(workspace_groups) > 1:
221
+ elif len(workspace_groups) > 1:
228
222
  ids = ', '.join(x.id for x in workspace_groups)
229
223
  raise ValueError(
230
224
  f'more than one workspace group with given name was found: {ids}',
231
225
  )
232
226
 
233
- if len(starter_workspaces) > 1:
227
+ # Starter workspace
228
+ starter_workspaces = [
229
+ x for x in manager.starter_workspaces
230
+ if x.name == deployment_name
231
+ ]
232
+
233
+ if len(starter_workspaces) == 1:
234
+ return starter_workspaces[0]
235
+
236
+ elif len(starter_workspaces) > 1:
234
237
  ids = ', '.join(x.id for x in starter_workspaces)
235
238
  raise ValueError(
236
239
  f'more than one starter workspace with given name was found: {ids}',
237
240
  )
238
241
 
239
- if workspace_groups:
240
- return workspace_groups[0]
241
- else:
242
- return starter_workspaces[0]
242
+ raise KeyError(f'no deployment found with name: {deployment_name}')
243
243
 
244
+ #
245
+ # Search for deployment by ID
246
+ #
244
247
  deployment_id = params.get('deployment_id') or \
245
248
  (params.get('in_deployment') or {}).get('deployment_id') or \
246
249
  (params.get('group') or {}).get('deployment_id') or \
247
250
  ((params.get('in') or {}).get('in_group') or {}).get('deployment_id') or \
248
251
  ((params.get('in') or {}).get('in_deployment') or {}).get('deployment_id')
252
+
249
253
  if deployment_id:
250
254
  try:
255
+ # Standard workspace group
251
256
  return manager.get_workspace_group(deployment_id)
252
257
  except ManagementError as exc:
253
258
  if exc.errno == 404:
254
259
  try:
260
+ # Starter workspace
255
261
  return manager.get_starter_workspace(deployment_id)
256
262
  except ManagementError as exc:
257
263
  if exc.errno == 404:
@@ -260,6 +266,7 @@ def get_deployment(
260
266
  else:
261
267
  raise
262
268
 
269
+ # Use workspace group from environment
263
270
  if os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP'):
264
271
  try:
265
272
  return manager.get_workspace_group(
@@ -273,6 +280,7 @@ def get_deployment(
273
280
  )
274
281
  raise
275
282
 
283
+ # Use cluster from environment
276
284
  if os.environ.get('SINGLESTOREDB_CLUSTER'):
277
285
  try:
278
286
  return manager.get_starter_workspace(
@@ -43,6 +43,12 @@ try:
43
43
  except ImportError:
44
44
  has_shapely = False
45
45
 
46
+ try:
47
+ import pydantic
48
+ has_pydantic = True
49
+ except ImportError:
50
+ has_pydantic = False
51
+
46
52
  from .. import connection
47
53
  from .. import fusion
48
54
  from .. import types
@@ -533,6 +539,9 @@ class Cursor(connection.Cursor):
533
539
  self._expect_results = True
534
540
  sql_type = 'query'
535
541
 
542
+ if has_pydantic and isinstance(params, pydantic.BaseModel):
543
+ params = params.model_dump()
544
+
536
545
  self._validate_param_subs(oper, params)
537
546
 
538
547
  handler = fusion.get_handler(oper)
@@ -348,6 +348,7 @@ class FilesObjectBytesReader(io.BytesIO):
348
348
 
349
349
 
350
350
  class FileLocation(ABC):
351
+
351
352
  @abstractmethod
352
353
  def open(
353
354
  self,
@@ -8,6 +8,12 @@ from ..utils import results
8
8
  from ..utils.debug import log_query
9
9
  from ..utils.results import get_schema
10
10
 
11
+ try:
12
+ from pydantic import BaseModel
13
+ has_pydantic = True
14
+ except ImportError:
15
+ has_pydantic = False
16
+
11
17
 
12
18
  #: Regular expression for :meth:`Cursor.executemany`.
13
19
  #: executemany only supports simple bulk insert.
@@ -149,6 +155,8 @@ class Cursor(BaseCursor):
149
155
  return tuple(literal(arg) for arg in args)
150
156
  elif dtype is dict or isinstance(args, dict):
151
157
  return {key: literal(val) for (key, val) in args.items()}
158
+ elif has_pydantic and isinstance(args, BaseModel):
159
+ return {key: literal(val) for (key, val) in args.model_dump().items()}
152
160
  # If it's not a dictionary let's try escaping it anyways.
153
161
  # Worst case it will throw a Value error
154
162
  return conn.escape(args)
@@ -6,6 +6,7 @@ import decimal
6
6
  import math
7
7
  import os
8
8
  import unittest
9
+ from typing import Optional
9
10
 
10
11
  from requests.exceptions import InvalidJSONError
11
12
 
@@ -28,6 +29,12 @@ try:
28
29
  except ImportError:
29
30
  has_pygeos = False
30
31
 
32
+ try:
33
+ import pydantic
34
+ has_pydantic = True
35
+ except ImportError:
36
+ has_pydantic = False
37
+
31
38
  import singlestoredb as s2
32
39
  from . import utils
33
40
  # import traceback
@@ -1255,6 +1262,68 @@ class TestBasics(unittest.TestCase):
1255
1262
  except Exception:
1256
1263
  pass
1257
1264
 
1265
+ def test_pydantic(self):
1266
+ if not has_pydantic:
1267
+ self.skipTest('Test requires pydantic')
1268
+
1269
+ tblname = 'foo_' + str(id(self))
1270
+
1271
+ class FooData(pydantic.BaseModel):
1272
+ x: Optional[int]
1273
+ y: Optional[float]
1274
+ z: Optional[str] = None
1275
+
1276
+ self.cur.execute(f'''
1277
+ CREATE TABLE {tblname}(
1278
+ x INT,
1279
+ y DOUBLE,
1280
+ z TEXT
1281
+ )
1282
+ ''')
1283
+
1284
+ self.cur.execute(
1285
+ f'INSERT INTO {tblname}(x, y) VALUES (%(x)s, %(y)s)',
1286
+ FooData(x=2, y=3.23),
1287
+ )
1288
+
1289
+ self.cur.execute('SELECT * FROM ' + tblname)
1290
+
1291
+ assert list(sorted(self.cur.fetchall())) == \
1292
+ list(sorted([(2, 3.23, None)]))
1293
+
1294
+ self.cur.executemany(
1295
+ f'INSERT INTO {tblname}(x) VALUES (%(x)s)',
1296
+ [FooData(x=3, y=3.12), FooData(x=10, y=100.11)],
1297
+ )
1298
+
1299
+ self.cur.execute('SELECT * FROM ' + tblname)
1300
+
1301
+ assert list(sorted(self.cur.fetchall())) == \
1302
+ list(
1303
+ sorted([
1304
+ (2, 3.23, None),
1305
+ (3, None, None),
1306
+ (10, None, None),
1307
+ ]),
1308
+ )
1309
+
1310
+ def test_charset(self):
1311
+ with s2.connect(database=type(self).dbname) as conn:
1312
+ with conn.cursor() as cur:
1313
+ cur.execute('''
1314
+ select json_extract_string('{"foo":"😀"}', "bar");
1315
+ ''')
1316
+
1317
+ if 'http' in self.conn.driver:
1318
+ self.skipTest('Charset is not use in HTTP interface')
1319
+
1320
+ with self.assertRaises(s2.OperationalError):
1321
+ with s2.connect(database=type(self).dbname, charset='utf8') as conn:
1322
+ with conn.cursor() as cur:
1323
+ cur.execute('''
1324
+ select json_extract_string('{"foo":"😀"}', "bar");
1325
+ ''')
1326
+
1258
1327
 
1259
1328
  if __name__ == '__main__':
1260
1329
  import nose2
@@ -4,6 +4,7 @@
4
4
  import os
5
5
  import random
6
6
  import secrets
7
+ import tempfile
7
8
  import time
8
9
  import unittest
9
10
  from typing import Any
@@ -714,3 +715,810 @@ class TestJobsFusion(unittest.TestCase):
714
715
  res = out[0]
715
716
  assert res[0] == job_id
716
717
  assert res[1] == 1
718
+
719
+
720
+ @pytest.mark.management
721
+ class TestStageFusion(unittest.TestCase):
722
+
723
+ id: str = secrets.token_hex(8)
724
+ dbname: str = 'information_schema'
725
+ manager: None
726
+ workspace_group: None
727
+ workspace_group_2: None
728
+
729
+ @classmethod
730
+ def setUpClass(cls):
731
+ cls.manager = s2.manage_workspaces()
732
+ us_regions = [x for x in cls.manager.regions if x.name.startswith('US')]
733
+ cls.workspace_group = cls.manager.create_workspace_group(
734
+ f'Stage Fusion Testing 1 {cls.id}',
735
+ region=random.choice(us_regions),
736
+ firewall_ranges=[],
737
+ )
738
+ cls.workspace_group_2 = cls.manager.create_workspace_group(
739
+ f'Stage Fusion Testing 2 {cls.id}',
740
+ region=random.choice(us_regions),
741
+ firewall_ranges=[],
742
+ )
743
+ # Wait for both workspace groups to start
744
+ time.sleep(5)
745
+
746
+ os.environ['SINGLESTOREDB_DEFAULT_DATABASE'] = 'information_schema'
747
+ os.environ['SINGLESTOREDB_WORKSPACE_GROUP'] = cls.workspace_group.id
748
+
749
+ @classmethod
750
+ def tearDownClass(cls):
751
+ if cls.workspace_group is not None:
752
+ cls.workspace_group.terminate(force=True)
753
+ if cls.workspace_group_2 is not None:
754
+ cls.workspace_group_2.terminate(force=True)
755
+ cls.manager = None
756
+ cls.workspace_group = None
757
+ cls.workspace_group_2 = None
758
+ cls.workspace = None
759
+ cls.workspace_2 = None
760
+ if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:
761
+ del os.environ['SINGLESTOREDB_WORKSPACE']
762
+ if os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP', None) is not None:
763
+ del os.environ['SINGLESTOREDB_WORKSPACE_GROUP']
764
+ if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:
765
+ del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']
766
+
767
+ def setUp(self):
768
+ self.enabled = os.environ.get('SINGLESTOREDB_FUSION_ENABLED')
769
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = '1'
770
+ self.conn = s2.connect(database=type(self).dbname, local_infile=True)
771
+ self.cur = self.conn.cursor()
772
+
773
+ def tearDown(self):
774
+ self._clear_stage()
775
+
776
+ if self.enabled:
777
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = self.enabled
778
+ else:
779
+ del os.environ['SINGLESTOREDB_FUSION_ENABLED']
780
+
781
+ try:
782
+ if self.cur is not None:
783
+ self.cur.close()
784
+ except Exception:
785
+ # traceback.print_exc()
786
+ pass
787
+
788
+ try:
789
+ if self.conn is not None:
790
+ self.conn.close()
791
+ except Exception:
792
+ # traceback.print_exc()
793
+ pass
794
+
795
+ def _clear_stage(self):
796
+ if self.workspace_group is not None:
797
+ self.cur.execute(f'''
798
+ show stage files
799
+ in group id '{self.workspace_group.id}' recursive
800
+ ''')
801
+ files = list(self.cur)
802
+ folders = []
803
+ for file in files:
804
+ if file[0].endswith('/'):
805
+ folders.append(file)
806
+ continue
807
+ self.cur.execute(f'''
808
+ drop stage file '{file[0]}'
809
+ in group id '{self.workspace_group.id}'
810
+ ''')
811
+ for folder in folders:
812
+ self.cur.execute(f'''
813
+ drop stage folder '{folder[0]}'
814
+ in group id '{self.workspace_group.id}'
815
+ ''')
816
+
817
+ if self.workspace_group_2 is not None:
818
+ self.cur.execute(f'''
819
+ show stage files
820
+ in group id '{self.workspace_group_2.id}' recursive
821
+ ''')
822
+ files = list(self.cur)
823
+ folders = []
824
+ for file in files:
825
+ if file[0].endswith('/'):
826
+ folders.append(file)
827
+ continue
828
+ self.cur.execute(f'''
829
+ drop stage file '{file[0]}'
830
+ in group id '{self.workspace_group_2.id}'
831
+ ''')
832
+ for folder in folders:
833
+ self.cur.execute(f'''
834
+ drop stage folder '{folder[0]}'
835
+ in group id '{self.workspace_group_2.id}'
836
+ ''')
837
+
838
+ def test_show_stage(self):
839
+ test2_sql = os.path.join(os.path.dirname(__file__), 'test2.sql')
840
+
841
+ # Should be empty
842
+ self.cur.execute('''
843
+ show stage files
844
+ ''')
845
+ files = list(self.cur)
846
+ assert len(files) == 0
847
+
848
+ # Copy files to stage
849
+ self.cur.execute(
850
+ f'upload file to stage "new_test_1.sql" from "{test2_sql}"',
851
+ )
852
+ self.cur.execute('create stage folder "subdir1"')
853
+ self.cur.execute(
854
+ f'upload file to stage "subdir1/new_test_2.sql" from "{test2_sql}"',
855
+ )
856
+ self.cur.execute(
857
+ f'upload file to stage "subdir1/new_test_3.sql" from "{test2_sql}"',
858
+ )
859
+ self.cur.execute('create stage folder "subdir2"')
860
+ self.cur.execute(
861
+ f'upload file to stage "subdir2/new_test_4.sql" from "{test2_sql}"',
862
+ )
863
+ self.cur.execute(
864
+ f'upload file to stage "subdir2/new_test_5.sql" from "{test2_sql}"',
865
+ )
866
+
867
+ # Make sure files are there
868
+ self.cur.execute('''
869
+ show stage files recursive
870
+ ''')
871
+ files = list(self.cur)
872
+ assert len(files) == 7
873
+ assert list(sorted(x[0] for x in files)) == [
874
+ 'new_test_1.sql',
875
+ 'subdir1/',
876
+ 'subdir1/new_test_2.sql',
877
+ 'subdir1/new_test_3.sql',
878
+ 'subdir2/',
879
+ 'subdir2/new_test_4.sql',
880
+ 'subdir2/new_test_5.sql',
881
+ ]
882
+
883
+ # Do non-recursive listing
884
+ self.cur.execute('''
885
+ show stage files
886
+ ''')
887
+ files = list(self.cur)
888
+ assert len(files) == 3
889
+ assert list(sorted(x[0] for x in files)) == [
890
+ 'new_test_1.sql',
891
+ 'subdir1/',
892
+ 'subdir2/',
893
+ ]
894
+
895
+ # List files in specific workspace group
896
+ self.cur.execute(f'''
897
+ show stage files in group id '{self.workspace_group.id}'
898
+ ''')
899
+ files = list(self.cur)
900
+ assert len(files) == 3
901
+ assert list(sorted(x[0] for x in files)) == [
902
+ 'new_test_1.sql',
903
+ 'subdir1/',
904
+ 'subdir2/',
905
+ ]
906
+
907
+ self.cur.execute(f'''
908
+ show stage files in id '{self.workspace_group.id}'
909
+ ''')
910
+ files = list(self.cur)
911
+ assert len(files) == 3
912
+ assert list(sorted(x[0] for x in files)) == [
913
+ 'new_test_1.sql',
914
+ 'subdir1/',
915
+ 'subdir2/',
916
+ ]
917
+
918
+ self.cur.execute(f'''
919
+ show stage files in group '{self.workspace_group.name}'
920
+ ''')
921
+ files = list(self.cur)
922
+ assert len(files) == 3
923
+ assert list(sorted(x[0] for x in files)) == [
924
+ 'new_test_1.sql',
925
+ 'subdir1/',
926
+ 'subdir2/',
927
+ ]
928
+
929
+ self.cur.execute(f'''
930
+ show stage files in '{self.workspace_group.name}'
931
+ ''')
932
+ files = list(self.cur)
933
+ assert len(files) == 3
934
+ assert list(sorted(x[0] for x in files)) == [
935
+ 'new_test_1.sql',
936
+ 'subdir1/',
937
+ 'subdir2/',
938
+ ]
939
+
940
+ # Check other workspace group
941
+ self.cur.execute(f'''
942
+ show stage files in group '{self.workspace_group_2.name}'
943
+ ''')
944
+ files = list(self.cur)
945
+ assert len(files) == 0
946
+
947
+ # Limit results
948
+ self.cur.execute('''
949
+ show stage files recursive limit 5
950
+ ''')
951
+ files = list(self.cur)
952
+ assert len(files) == 5
953
+ assert list(sorted(x[0] for x in files)) == [
954
+ 'new_test_1.sql',
955
+ 'subdir1/',
956
+ 'subdir1/new_test_2.sql',
957
+ 'subdir1/new_test_3.sql',
958
+ 'subdir2/',
959
+ ]
960
+
961
+ # Order by type and name
962
+ self.cur.execute('''
963
+ show stage files order by type, name recursive extended
964
+ ''')
965
+ files = list(self.cur)
966
+ assert len(files) == 7
967
+ assert list(x[0] for x in files) == [
968
+ 'subdir1/',
969
+ 'subdir2/',
970
+ 'new_test_1.sql',
971
+ 'subdir1/new_test_2.sql',
972
+ 'subdir1/new_test_3.sql',
973
+ 'subdir2/new_test_4.sql',
974
+ 'subdir2/new_test_5.sql',
975
+ ]
976
+
977
+ # Order by type and name descending
978
+ self.cur.execute('''
979
+ show stage files order by type desc, name desc recursive extended
980
+ ''')
981
+ files = list(self.cur)
982
+ assert len(files) == 7
983
+ assert list(x[0] for x in files) == [
984
+ 'subdir2/new_test_5.sql',
985
+ 'subdir2/new_test_4.sql',
986
+ 'subdir1/new_test_3.sql',
987
+ 'subdir1/new_test_2.sql',
988
+ 'new_test_1.sql',
989
+ 'subdir2/',
990
+ 'subdir1/',
991
+ ]
992
+
993
+ # List at specific path
994
+ self.cur.execute('''
995
+ show stage files at 'subdir2/' recursive
996
+ ''')
997
+ files = list(self.cur)
998
+ assert len(files) == 2
999
+ assert list(sorted(x[0] for x in files)) == [
1000
+ 'new_test_4.sql',
1001
+ 'new_test_5.sql',
1002
+ ]
1003
+
1004
+ # LIKE clause
1005
+ self.cur.execute('''
1006
+ show stage files like '%_4.%' recursive
1007
+ ''')
1008
+ files = list(self.cur)
1009
+ assert len(files) == 1
1010
+ assert list(sorted(x[0] for x in files)) == [
1011
+ 'subdir2/new_test_4.sql',
1012
+ ]
1013
+
1014
+ def test_download_stage(self):
1015
+ test2_sql = os.path.join(os.path.dirname(__file__), 'test2.sql')
1016
+
1017
+ # Should be empty
1018
+ self.cur.execute('''
1019
+ show stage files
1020
+ ''')
1021
+ files = list(self.cur)
1022
+ assert len(files) == 0
1023
+
1024
+ # Copy file to stage 1
1025
+ self.cur.execute(f'''
1026
+ upload file to stage 'dl_test.sql' from '{test2_sql}'
1027
+ ''')
1028
+
1029
+ self.cur.execute('''
1030
+ show stage files
1031
+ ''')
1032
+ files = list(self.cur)
1033
+ assert len(files) == 1
1034
+ assert list(sorted(x[0] for x in files)) == ['dl_test.sql']
1035
+
1036
+ # Copy file to stage 2
1037
+ self.cur.execute(f'''
1038
+ upload file to stage 'dl_test2.sql'
1039
+ in group '{self.workspace_group_2.name}'
1040
+ from '{test2_sql}'
1041
+ ''')
1042
+
1043
+ # Make sure only one file in stage 2
1044
+ self.cur.execute(f'''
1045
+ show stage files in group '{self.workspace_group_2.name}'
1046
+ ''')
1047
+ files = list(self.cur)
1048
+ assert len(files) == 1
1049
+ assert list(sorted(x[0] for x in files)) == ['dl_test2.sql']
1050
+
1051
+ # Download file from stage 1
1052
+ with tempfile.TemporaryDirectory() as tmpdir:
1053
+ self.cur.execute(f'''
1054
+ download stage file 'dl_test.sql' to '{tmpdir}/dl_test.sql'
1055
+ ''')
1056
+ with open(os.path.join(tmpdir, 'dl_test.sql'), 'r') as dl_file:
1057
+ assert dl_file.read() == open(test2_sql, 'r').read()
1058
+
1059
+ # Download file from stage 2
1060
+ with tempfile.TemporaryDirectory() as tmpdir:
1061
+ self.cur.execute(f'''
1062
+ download stage file 'dl_test2.sql'
1063
+ in group '{self.workspace_group_2.name}'
1064
+ to '{tmpdir}/dl_test2.sql'
1065
+ ''')
1066
+ with open(os.path.join(tmpdir, 'dl_test2.sql'), 'r') as dl_file:
1067
+ assert dl_file.read() == open(test2_sql, 'r').read()
1068
+
1069
+ def test_stage_multi_wg_operations(self):
1070
+ test_sql = os.path.join(os.path.dirname(__file__), 'test.sql')
1071
+ test2_sql = os.path.join(os.path.dirname(__file__), 'test2.sql')
1072
+
1073
+ # Should be empty
1074
+ self.cur.execute('''
1075
+ show stage files
1076
+ ''')
1077
+ files = list(self.cur)
1078
+ assert len(files) == 0
1079
+
1080
+ # Copy file to stage 1
1081
+ self.cur.execute(f'''
1082
+ upload file to stage 'new_test.sql' from '{test_sql}'
1083
+ ''')
1084
+
1085
+ self.cur.execute('''
1086
+ show stage files
1087
+ ''')
1088
+ files = list(self.cur)
1089
+ assert len(files) == 1
1090
+
1091
+ # Copy file to stage 2
1092
+ self.cur.execute(f'''
1093
+ upload file to stage 'new_test2.sql'
1094
+ in group '{self.workspace_group_2.name}'
1095
+ from '{test2_sql}'
1096
+ ''')
1097
+
1098
+ # Make sure only one file in stage 1
1099
+ self.cur.execute('''
1100
+ show stage files
1101
+ ''')
1102
+ files = list(self.cur)
1103
+ assert len(files) == 1
1104
+ assert files[0][0] == 'new_test.sql'
1105
+
1106
+ # Make sure only one file in stage 2
1107
+ self.cur.execute(f'''
1108
+ show stage files in group '{self.workspace_group_2.name}' recursive
1109
+ ''')
1110
+ files = list(self.cur)
1111
+ assert len(files) == 1
1112
+ assert list(sorted(x[0] for x in files)) == ['new_test2.sql']
1113
+
1114
+ # Make sure only one file in stage 2 (using IN)
1115
+ self.cur.execute(f'''
1116
+ show stage files in '{self.workspace_group_2.name}' recursive
1117
+ ''')
1118
+ files = list(self.cur)
1119
+ assert len(files) == 1
1120
+ assert list(sorted(x[0] for x in files)) == ['new_test2.sql']
1121
+
1122
+ # Make subdir
1123
+ self.cur.execute(f'''
1124
+ create stage folder 'data' in group '{self.workspace_group_2.name}'
1125
+ ''')
1126
+
1127
+ # Upload file using workspace ID
1128
+ self.cur.execute(f'''
1129
+ upload file to stage 'data/new_test2_sub.sql'
1130
+ in group id '{self.workspace_group_2.id}'
1131
+ from '{test2_sql}'
1132
+ ''')
1133
+
1134
+ # Make sure only one file in stage 1
1135
+ self.cur.execute('''
1136
+ show stage files
1137
+ ''')
1138
+ files = list(self.cur)
1139
+ assert len(files) == 1
1140
+ assert files[0][0] == 'new_test.sql'
1141
+
1142
+ # Make sure two files in stage 2
1143
+ self.cur.execute(f'''
1144
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1145
+ ''')
1146
+ files = list(self.cur)
1147
+ assert len(files) == 3
1148
+ assert list(sorted(x[0] for x in files)) == \
1149
+ ['data/', 'data/new_test2_sub.sql', 'new_test2.sql']
1150
+
1151
+ # Test overwrite
1152
+ with self.assertRaises(OSError):
1153
+ self.cur.execute(f'''
1154
+ upload file to stage 'data/new_test2_sub.sql'
1155
+ in group id '{self.workspace_group_2.id}'
1156
+ from '{test2_sql}'
1157
+ ''')
1158
+
1159
+ self.cur.execute(f'''
1160
+ upload file to stage 'data/new_test2_sub.sql'
1161
+ in group id '{self.workspace_group_2.id}'
1162
+ from '{test2_sql}' overwrite
1163
+ ''')
1164
+
1165
+ # Make sure two files in stage 2
1166
+ self.cur.execute(f'''
1167
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1168
+ ''')
1169
+ files = list(self.cur)
1170
+ assert len(files) == 3
1171
+ assert list(sorted(x[0] for x in files)) == \
1172
+ ['data/', 'data/new_test2_sub.sql', 'new_test2.sql']
1173
+
1174
+ # Test LIKE clause
1175
+ self.cur.execute(f'''
1176
+ show stage files
1177
+ in group id '{self.workspace_group_2.id}'
1178
+ like '%_sub%' recursive
1179
+ ''')
1180
+ files = list(self.cur)
1181
+ assert len(files) == 1
1182
+ assert list(sorted(x[0] for x in files)) == ['data/new_test2_sub.sql']
1183
+
1184
+ # Drop file from default stage
1185
+ self.cur.execute('''
1186
+ drop stage file 'new_test.sql'
1187
+ ''')
1188
+
1189
+ # Make sure no files in stage 1
1190
+ self.cur.execute('''
1191
+ show stage files
1192
+ ''')
1193
+ files = list(self.cur)
1194
+ assert len(files) == 0
1195
+
1196
+ # Make sure two files in stage 2
1197
+ self.cur.execute(f'''
1198
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1199
+ ''')
1200
+ files = list(self.cur)
1201
+ assert len(files) == 3
1202
+ assert list(sorted(x[0] for x in files)) == \
1203
+ ['data/', 'data/new_test2_sub.sql', 'new_test2.sql']
1204
+
1205
+ # Attempt to drop directory from stage 2
1206
+ with self.assertRaises(OSError):
1207
+ self.cur.execute(f'''
1208
+ drop stage folder 'data'
1209
+ in group id '{self.workspace_group_2.id}'
1210
+ ''')
1211
+
1212
+ self.cur.execute(f'''
1213
+ drop stage file 'data/new_test2_sub.sql'
1214
+ in group id '{self.workspace_group_2.id}'
1215
+ ''')
1216
+
1217
+ # Make sure one file and one directory in stage 2
1218
+ self.cur.execute(f'''
1219
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1220
+ ''')
1221
+ files = list(self.cur)
1222
+ assert len(files) == 2
1223
+ assert list(sorted(x[0] for x in files)) == ['data/', 'new_test2.sql']
1224
+
1225
+ # Drop stage folder from stage 2
1226
+ self.cur.execute(f'''
1227
+ drop stage folder 'data'
1228
+ in group id '{self.workspace_group_2.id}'
1229
+ ''')
1230
+
1231
+ # Make sure one file in stage 2
1232
+ self.cur.execute(f'''
1233
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1234
+ ''')
1235
+ files = list(self.cur)
1236
+ assert len(files) == 1
1237
+ assert list(sorted(x[0] for x in files)) == ['new_test2.sql']
1238
+
1239
+ # Drop last file
1240
+ self.cur.execute(f'''
1241
+ drop stage file 'new_test2.sql'
1242
+ in group id '{self.workspace_group_2.id}'
1243
+ ''')
1244
+
1245
+ # Make sure no files in stage 2
1246
+ self.cur.execute(f'''
1247
+ show stage files in group id '{self.workspace_group_2.id}' recursive
1248
+ ''')
1249
+ files = list(self.cur)
1250
+ assert len(files) == 0
1251
+
1252
+
1253
+ @pytest.mark.management
1254
+ class TestFilesFusion(unittest.TestCase):
1255
+
1256
+ id: str = secrets.token_hex(8)
1257
+ dbname: str = 'information_schema'
1258
+ manager: None
1259
+ workspace_group: None
1260
+
1261
+ @classmethod
1262
+ def setUpClass(cls):
1263
+ cls.manager = s2.manage_workspaces()
1264
+ us_regions = [x for x in cls.manager.regions if x.name.startswith('US')]
1265
+ cls.workspace_group = cls.manager.create_workspace_group(
1266
+ f'Files Fusion Testing {cls.id}',
1267
+ region=random.choice(us_regions),
1268
+ firewall_ranges=[],
1269
+ )
1270
+ # Wait for both workspace groups to start
1271
+ time.sleep(5)
1272
+
1273
+ os.environ['SINGLESTOREDB_DEFAULT_DATABASE'] = 'information_schema'
1274
+ os.environ['SINGLESTOREDB_WORKSPACE_GROUP'] = cls.workspace_group.id
1275
+
1276
+ @classmethod
1277
+ def tearDownClass(cls):
1278
+ if cls.workspace_group is not None:
1279
+ cls.workspace_group.terminate(force=True)
1280
+ cls.manager = None
1281
+ cls.workspace_group = None
1282
+ cls.workspace = None
1283
+ if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:
1284
+ del os.environ['SINGLESTOREDB_WORKSPACE']
1285
+ if os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP', None) is not None:
1286
+ del os.environ['SINGLESTOREDB_WORKSPACE_GROUP']
1287
+ if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:
1288
+ del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']
1289
+
1290
+ def setUp(self):
1291
+ self.enabled = os.environ.get('SINGLESTOREDB_FUSION_ENABLED')
1292
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = '1'
1293
+ self.conn = s2.connect(database=type(self).dbname, local_infile=True)
1294
+ self.cur = self.conn.cursor()
1295
+
1296
+ def tearDown(self):
1297
+ self._clear_files()
1298
+
1299
+ if self.enabled:
1300
+ os.environ['SINGLESTOREDB_FUSION_ENABLED'] = self.enabled
1301
+ else:
1302
+ del os.environ['SINGLESTOREDB_FUSION_ENABLED']
1303
+
1304
+ try:
1305
+ if self.cur is not None:
1306
+ self.cur.close()
1307
+ except Exception:
1308
+ # traceback.print_exc()
1309
+ pass
1310
+
1311
+ try:
1312
+ if self.conn is not None:
1313
+ self.conn.close()
1314
+ except Exception:
1315
+ # traceback.print_exc()
1316
+ pass
1317
+
1318
+ def _clear_files(self):
1319
+ cls = type(self)
1320
+ for prefix in ['show', 'dl', 'drop']:
1321
+ for i in range(1, 6):
1322
+ try:
1323
+ self.cur.execute(
1324
+ f'''drop personal file "{prefix}_test_{i}_{cls.id}.ipynb"''',
1325
+ )
1326
+ except (OSError, s2.ManagementError):
1327
+ pass
1328
+ for i in range(1, 6):
1329
+ try:
1330
+ self.cur.execute(
1331
+ f'''drop shared file "{prefix}_test_{i}_{cls.id}.ipynb"''',
1332
+ )
1333
+ except (OSError, s2.ManagementError):
1334
+ pass
1335
+
1336
+ def test_show_personal_files(self):
1337
+ return self._test_show_files('personal')
1338
+
1339
+ def test_show_shared_files(self):
1340
+ return self._test_show_files('shared')
1341
+
1342
+ def _test_show_files(self, ftype):
1343
+ cls = type(self)
1344
+ nb = os.path.join(os.path.dirname(__file__), 'test.ipynb')
1345
+
1346
+ # Should be empty
1347
+ self.cur.execute(f'''
1348
+ show {ftype} files like 'show_%{cls.id}%'
1349
+ ''')
1350
+ files = list(self.cur)
1351
+ assert len(files) == 0
1352
+
1353
+ # Upload files
1354
+ self.cur.execute(
1355
+ f'upload {ftype} file to "show_test_1_{cls.id}.ipynb" from "{nb}"',
1356
+ )
1357
+ self.cur.execute(
1358
+ f'upload {ftype} file to "show_test_2_{cls.id}.ipynb" from "{nb}"',
1359
+ )
1360
+ self.cur.execute(
1361
+ f'upload {ftype} file to "show_test_3_{cls.id}.ipynb" from "{nb}"',
1362
+ )
1363
+ self.cur.execute(
1364
+ f'upload {ftype} file to "show_test_4_{cls.id}.ipynb" from "{nb}"',
1365
+ )
1366
+ self.cur.execute(
1367
+ f'upload {ftype} file to "show_test_5_{cls.id}.ipynb" from "{nb}"',
1368
+ )
1369
+
1370
+ # Make sure files are there
1371
+ self.cur.execute(f'''
1372
+ show {ftype} files like 'show_%{cls.id}%'
1373
+ ''')
1374
+ files = list(self.cur)
1375
+ assert len(files) == 5
1376
+ assert list(sorted(x[0] for x in files)) == [
1377
+ f'show_test_1_{cls.id}.ipynb',
1378
+ f'show_test_2_{cls.id}.ipynb',
1379
+ f'show_test_3_{cls.id}.ipynb',
1380
+ f'show_test_4_{cls.id}.ipynb',
1381
+ f'show_test_5_{cls.id}.ipynb',
1382
+ ]
1383
+
1384
+ # Test ORDER BY
1385
+ self.cur.execute(f'''
1386
+ show {ftype} files like 'show_%{cls.id}%' order by name desc
1387
+ ''')
1388
+ files = list(self.cur)
1389
+ assert len(files) == 5
1390
+ assert list(x[0] for x in files) == [
1391
+ f'show_test_5_{cls.id}.ipynb',
1392
+ f'show_test_4_{cls.id}.ipynb',
1393
+ f'show_test_3_{cls.id}.ipynb',
1394
+ f'show_test_2_{cls.id}.ipynb',
1395
+ f'show_test_1_{cls.id}.ipynb',
1396
+ ]
1397
+
1398
+ # Test LIMIT
1399
+ self.cur.execute(f'''
1400
+ show {ftype} files like 'show_%{cls.id}%' order by name desc limit 3
1401
+ ''')
1402
+ files = list(self.cur)
1403
+ assert len(files) == 3
1404
+ assert list(x[0] for x in files) == [
1405
+ f'show_test_5_{cls.id}.ipynb',
1406
+ f'show_test_4_{cls.id}.ipynb',
1407
+ f'show_test_3_{cls.id}.ipynb',
1408
+ ]
1409
+
1410
+ # Test EXTENDED
1411
+ self.cur.execute(f'''
1412
+ show {ftype} files like 'show_%{cls.id}%' extended
1413
+ ''')
1414
+ assert [x[0] for x in self.cur.description] == \
1415
+ ['Name', 'Type', 'Size', 'Writable', 'CreatedAt', 'LastModifiedAt']
1416
+
1417
+ def test_download_personal_files(self):
1418
+ return self._test_download_files('personal')
1419
+
1420
+ def test_download_shared_files(self):
1421
+ return self._test_download_files('shared')
1422
+
1423
+ def _test_download_files(self, ftype):
1424
+ cls = type(self)
1425
+ nb = os.path.join(os.path.dirname(__file__), 'test.ipynb')
1426
+
1427
+ # Should be empty
1428
+ self.cur.execute(f'''
1429
+ show {ftype} files like 'dl_%{cls.id}%'
1430
+ ''')
1431
+ files = list(self.cur)
1432
+ assert len(files) == 0
1433
+
1434
+ # Upload files
1435
+ self.cur.execute(f'upload {ftype} file to "dl_test_1_{cls.id}.ipynb" from "{nb}"')
1436
+ self.cur.execute(f'upload {ftype} file to "dl_test_2_{cls.id}.ipynb" from "{nb}"')
1437
+
1438
+ # Make sure files are there
1439
+ self.cur.execute(f'''
1440
+ show {ftype} files like 'dl_%{cls.id}%'
1441
+ ''')
1442
+ files = list(self.cur)
1443
+ assert len(files) == 2
1444
+ assert list(sorted(x[0] for x in files)) == [
1445
+ f'dl_test_1_{cls.id}.ipynb',
1446
+ f'dl_test_2_{cls.id}.ipynb',
1447
+ ]
1448
+
1449
+ # Download files
1450
+ with tempfile.TemporaryDirectory() as tmpdir:
1451
+ self.cur.execute(f'''
1452
+ download {ftype} file 'dl_test_1_{cls.id}.ipynb'
1453
+ to '{tmpdir}/dl_test_1.ipynb'
1454
+ ''')
1455
+ with open(os.path.join(tmpdir, 'dl_test_1.ipynb'), 'r') as dl_file:
1456
+ assert dl_file.read() == open(nb, 'r').read()
1457
+
1458
+ self.cur.execute(f'''
1459
+ download {ftype} file 'dl_test_2_{cls.id}.ipynb'
1460
+ to '{tmpdir}/dl_test_2.ipynb'
1461
+ ''')
1462
+ with open(os.path.join(tmpdir, 'dl_test_2.ipynb'), 'r') as dl_file:
1463
+ assert dl_file.read() == open(nb, 'r').read()
1464
+
1465
+ def test_drop_personal_files(self):
1466
+ return self._test_drop_files('personal')
1467
+
1468
+ def test_drop_shared_files(self):
1469
+ return self._test_drop_files('shared')
1470
+
1471
+ def _test_drop_files(self, ftype):
1472
+ cls = type(self)
1473
+ nb = os.path.join(os.path.dirname(__file__), 'test.ipynb')
1474
+
1475
+ # Should be empty
1476
+ self.cur.execute(f'''
1477
+ show {ftype} files like 'drop_%{cls.id}%'
1478
+ ''')
1479
+ files = list(self.cur)
1480
+ assert len(files) == 0
1481
+
1482
+ # Upload files
1483
+ self.cur.execute(
1484
+ f'upload {ftype} file to "drop_test_1_{cls.id}.ipynb" from "{nb}"',
1485
+ )
1486
+ self.cur.execute(
1487
+ f'upload {ftype} file to "drop_test_2_{cls.id}.ipynb" from "{nb}"',
1488
+ )
1489
+
1490
+ # Make sure files are there
1491
+ self.cur.execute(f'''
1492
+ show {ftype} files like 'drop_%{cls.id}%'
1493
+ ''')
1494
+ files = list(self.cur)
1495
+ assert len(files) == 2
1496
+ assert list(sorted(x[0] for x in files)) == [
1497
+ f'drop_test_1_{cls.id}.ipynb',
1498
+ f'drop_test_2_{cls.id}.ipynb',
1499
+ ]
1500
+
1501
+ # Drop 1 file
1502
+ self.cur.execute(f'''
1503
+ drop {ftype} file 'drop_test_1_{cls.id}.ipynb'
1504
+ ''')
1505
+
1506
+ # Make sure 1 file is there
1507
+ self.cur.execute(f'''
1508
+ show {ftype} files like 'drop_%{cls.id}%'
1509
+ ''')
1510
+ files = list(self.cur)
1511
+ assert len(files) == 1
1512
+ assert list(x[0] for x in files) == [f'drop_test_2_{cls.id}.ipynb']
1513
+
1514
+ # Drop 2nd file
1515
+ self.cur.execute(f'''
1516
+ drop {ftype} file 'drop_test_2_{cls.id}.ipynb'
1517
+ ''')
1518
+
1519
+ # Make sure no files are there
1520
+ self.cur.execute(f'''
1521
+ show {ftype} files like 'drop_%{cls.id}%'
1522
+ ''')
1523
+ files = list(self.cur)
1524
+ assert len(files) == 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.12.1
3
+ Version: 1.12.3
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
@@ -1,7 +1,7 @@
1
- _singlestoredb_accel.pyd,sha256=K84k9acANjxQTnOgN4bllLqP-W3_bRAyQXwiHW9Gnug,59392
2
- singlestoredb/__init__.py,sha256=OPUFo7H7ruOIJ8PhqbIqFM3SLvfG3ny3REXme5oFLhc,1712
1
+ _singlestoredb_accel.pyd,sha256=tWl_t2sd10DGMEUd3QEvL7DGh6nhah3stCLQU4W6ANo,59392
2
+ singlestoredb/__init__.py,sha256=Mt5lSYU33x17JRX_uk7ftkZ8RmWnyWINacQdbCFPSxY,1712
3
3
  singlestoredb/auth.py,sha256=RmYiH0Wlc2RXc4pTlRMysxtBI445ggCIwojWKC_eDLE,7844
4
- singlestoredb/config.py,sha256=5OY9RDlGBrBWAHsGrg0X4v0y_QwNb7G6FSQooMgT8rY,13032
4
+ singlestoredb/config.py,sha256=7-M2c7IAv3B1AmAKizgeNn880TDBbM4gbElLfStejIU,13035
5
5
  singlestoredb/connection.py,sha256=Ty_idVYH50Qx-j8WXy7NeB-DYLAcpdjGYTrTHkKzG9U,47309
6
6
  singlestoredb/converters.py,sha256=6gN3_RzSbw0Aimd5cGgBNPNq1yiHb1a_NK8qC9DmOQ0,21636
7
7
  singlestoredb/exceptions.py,sha256=WCCJrNSsU-hD-621Jpd6bwmvGftQ7byXkk-XKXlaxpg,3354
@@ -22,10 +22,10 @@ singlestoredb/apps/_uvicorn_util.py,sha256=Petkmq5keBPfXZsHBrnZfY3O2rUHvb3Cw6o-B
22
22
  singlestoredb/functions/__init__.py,sha256=Ehyp1pa40cvizzSYNGZ4UP4tiEcyfaxf_LI-oyt-Lro,84
23
23
  singlestoredb/functions/decorator.py,sha256=6c0uIknQNZrN5LAj-mWUw25Bcnl-_dRel6RMDzn2vK8,11816
24
24
  singlestoredb/functions/dtypes.py,sha256=5IwMSaSzxtSowxXrm5hZXW1lpNm6QILxiU4mAUEkBO0,32854
25
- singlestoredb/functions/signature.py,sha256=np4DHtfLM-S38srVu9KzsJZnI7tiwrl0UtMnA8F6rZ0,23162
25
+ singlestoredb/functions/signature.py,sha256=0d1mqH1J0cV7W1NBAqM2C98DWIZomF5qYSS5-T6OW_g,23324
26
26
  singlestoredb/functions/ext/__init__.py,sha256=5ppI8IZN_zOwoJFdu_Oq9ipxtyHw9n6OMVAa_s9T_yY,24
27
27
  singlestoredb/functions/ext/arrow.py,sha256=mQhwaMpvCH_dP92WIhP_j-stu272n4UAHsFUOBTgnq0,9436
28
- singlestoredb/functions/ext/asgi.py,sha256=ENwGjjLxFYjv6HFB5WdX4YT5GSs5sn3mirOLgAM0wTY,44807
28
+ singlestoredb/functions/ext/asgi.py,sha256=TNQlzosguNaM1-Rs1XXO78phav25XdKsbLTFpIvXgRA,45362
29
29
  singlestoredb/functions/ext/json.py,sha256=CROdj37cuJhAZ-CM93EI-SoLb4kxFcMGudsJ5QGyCoI,10831
30
30
  singlestoredb/functions/ext/mmap.py,sha256=zo6eweOFCZp0KIzAeL1vvuSjqvQhE8ybVhHbU0ZICt4,14124
31
31
  singlestoredb/functions/ext/rowdat_1.py,sha256=KYj_y5JWm3_B2-QC47HK-CNOrzujBqGUwLJfE49jwRg,23050
@@ -41,10 +41,10 @@ singlestoredb/fusion/handlers/files.py,sha256=pCx1sqnjPtQrp39rv_V4RX9CVtj6uSiL6H
41
41
  singlestoredb/fusion/handlers/job.py,sha256=3enfxHwERH7T4u0FEwOPN0IL0GtepaCYgEsisiy3Df4,21753
42
42
  singlestoredb/fusion/handlers/models.py,sha256=XWaPJQc3GQIOAcjNcxBSGUBJ3xu2qkzQ4ILa40TFQmY,6486
43
43
  singlestoredb/fusion/handlers/stage.py,sha256=PP-SSP204lwpmnycSXXSmFPzoN535JVuwglDCbaQ8Lw,14789
44
- singlestoredb/fusion/handlers/utils.py,sha256=2Lhnb9GqRMhCnO_C63iKt2hU7XFsllRKvQm3ChNzlrA,10890
44
+ singlestoredb/fusion/handlers/utils.py,sha256=nV2lSzKhv7CzM7I_uIh5kmDV0Ec6VeeKoHczx5pVNcw,11009
45
45
  singlestoredb/fusion/handlers/workspace.py,sha256=NxoEY5xd5lCQmXiim4nhAYCL0agHo1H_rGPpqa31hiw,28397
46
46
  singlestoredb/http/__init__.py,sha256=4cEDvLloGc3LSpU-PnIwacyu0n5oIIIE6xk2SPyWD_w,939
47
- singlestoredb/http/connection.py,sha256=dqXX5SSn6EOV7q0Ehh0E525eLT__B_jJLG-7jgVjbXg,40861
47
+ singlestoredb/http/connection.py,sha256=kLA-LA4zinbNOSemRbGqbTHd3bNn-ucOwmQZa8HIKPI,41075
48
48
  singlestoredb/magics/__init__.py,sha256=fqCBQ0s8o1CYE4Xo_XiSbkLDzLgMNDgpSkOx66-uDZw,1244
49
49
  singlestoredb/magics/run_personal.py,sha256=M11xHi9lWquh_pLSpFI89LGE7PhOPQOGqlSPDl48itE,1900
50
50
  singlestoredb/magics/run_shared.py,sha256=rnKpW4d8CJvD6ehK8jG8FlxuqZvjZl4KocPTsk-23O8,1805
@@ -52,7 +52,7 @@ singlestoredb/management/__init__.py,sha256=A66ZnFyX--PsAZ2tvtYUfIUBvVGDBFQsnVc6
52
52
  singlestoredb/management/billing_usage.py,sha256=0UHFSPCrN0nyeGFFM-HXS3NP8pYmYo2BCCahDEPXvzg,3883
53
53
  singlestoredb/management/cluster.py,sha256=auBzNYIXvnI6rq3DNpPgJhwWoT6JsyZRikjpON23Pxg,14867
54
54
  singlestoredb/management/export.py,sha256=2dCvnTnkxVI-arX3_375DtWzHkI1YNK5zaFYdHKE4cs,5277
55
- singlestoredb/management/files.py,sha256=rDXWmxVC-rx_ghHrCkdHyeBGGy6lb73wLWO6VkxdqMQ,31577
55
+ singlestoredb/management/files.py,sha256=Z9GpS2EHf9atE8kJdz1vJtsiT80O6TV00MPhqyXfAAw,31579
56
56
  singlestoredb/management/job.py,sha256=Npfe1JLYJlggGBrXLniPKwKUKF1i3alvSY1SFtvauSs,25498
57
57
  singlestoredb/management/manager.py,sha256=8zU0d7NG83PYMhoAs2JriTqbqh-R2tLX7VZoeZtcogY,9148
58
58
  singlestoredb/management/organization.py,sha256=JBsNC4R3boUKdYvyCZyfGoVMC1mD6SPuMI1UssBVoOM,5611
@@ -64,7 +64,7 @@ singlestoredb/mysql/_auth.py,sha256=YaqqyvAHmeraBv3BM207rNveUVPM-mPnW20ts_ynVWg,
64
64
  singlestoredb/mysql/charset.py,sha256=mnCdMpvdub1S2mm2PSk2j5JddgsWRjsVLtGx-y9TskE,10724
65
65
  singlestoredb/mysql/connection.py,sha256=Dlam7wNSfn4jVBNrqdvxLzwxJqCQUzuu_rlcKU45MB0,75113
66
66
  singlestoredb/mysql/converters.py,sha256=vebFFm6IrC0WgY-5Eh-esaPizY5cq3vDOUlEKGaYM-U,7771
67
- singlestoredb/mysql/cursors.py,sha256=pkrP-1t8IhBJRnYpdM7Rdm-332nOq1RYTDJ_yg_q5HI,27682
67
+ singlestoredb/mysql/cursors.py,sha256=YoZU5_weniqXcoeA0GVSxmetkPYooiDkXMbVBYUNlrU,27942
68
68
  singlestoredb/mysql/err.py,sha256=aDbmfq08gWVmfgIea735wSeiFdvYbB5wusgd3qTVq1s,2480
69
69
  singlestoredb/mysql/optionfile.py,sha256=bz0cZp8tQZvab1iU7OT0yldHyaMVbvAcUJ3TSNwcmyI,675
70
70
  singlestoredb/mysql/protocol.py,sha256=UzHcrv0Pgb1FNofuBTnSxpC9VDkgNbPEbBrRETstxAg,14888
@@ -113,14 +113,14 @@ singlestoredb/tests/test.ipynb,sha256=IEgXbByXyWDplZvod1J2SqNHZiPOGdD4oNVMd0ArP7
113
113
  singlestoredb/tests/test.sql,sha256=winJzhZ2W52PcQ1j8X_NNIDRBmEa-xMAYrS_hoUtAP8,18337
114
114
  singlestoredb/tests/test2.ipynb,sha256=_kBQVvEoinQ1zInNcWFKpbdw-djkLsEO8l3g2MEU_Zs,236
115
115
  singlestoredb/tests/test2.sql,sha256=CEM8_lX189iQU65G3Pod7lig6osfrveQyoDz6HC35YQ,38
116
- singlestoredb/tests/test_basics.py,sha256=tLiR46qUy8-OHHRSHsQTBp5q9NAwNPR9HvfutI6YgnM,47629
116
+ singlestoredb/tests/test_basics.py,sha256=7JZdyudRaW3ZJkQ-m21ZstFlHz1Fyw2eSfN7y_zEhpU,49703
117
117
  singlestoredb/tests/test_config.py,sha256=Ad0PDmCnJMOyy9f7WTKiRasSR_3mYRByUlSb7k5ZySg,11502
118
118
  singlestoredb/tests/test_connection.py,sha256=UmoNo8erkcifEMbHZl83yAaRsyh6HANRdEtY3aViOK8,122792
119
119
  singlestoredb/tests/test_dbapi.py,sha256=cNJoTEZvYG7ckcwT7xqlkJX-2TDEYGTDDU1Igucp48k,679
120
120
  singlestoredb/tests/test_exceptions.py,sha256=vscMYmdOJr0JmkTAJrNI2w0Q96Nfugjkrt5_lYnw8i0,1176
121
121
  singlestoredb/tests/test_ext_func.py,sha256=gQErR-wAN8BqLNG5U4pNbg4qkQEo6Re8Hd9_Ztqo1RM,38550
122
122
  singlestoredb/tests/test_ext_func_data.py,sha256=9kn8BWmCjkbnP6hSbFhmhcdW4OmVT-GSvBTIzFBLEys,48796
123
- singlestoredb/tests/test_fusion.py,sha256=ckATjXKcDBvej68PZgTRJirdoywtUJ7WXkZmfR0la_4,24544
123
+ singlestoredb/tests/test_fusion.py,sha256=S0Jk2NrcOitqM98r5fosHGbZ1sCZ2uxar5t48v-uOD0,52045
124
124
  singlestoredb/tests/test_http.py,sha256=7hwXe61hlUes3nji0MTTZweo94tJAlJ-vA5ct9geXFQ,8868
125
125
  singlestoredb/tests/test_management.py,sha256=EJY-JDFNS83_pM0KKJlEHoRiEZWHxjK2XWtM0YAy63U,46462
126
126
  singlestoredb/tests/test_plugin.py,sha256=P1nXLnTafaHkHN-6bVbGryxTu7OWJPU9SYFZ_WQUwq8,845
@@ -141,9 +141,9 @@ singlestoredb/utils/results.py,sha256=wR70LhCqlobniZf52r67zYLBOKjWHQm68NAskdRQND
141
141
  singlestoredb/utils/xdict.py,sha256=-wi1lSPTnY99fhVMBhPKJ8cCsQhNG4GMUfkEBDKYgCw,13321
142
142
  sqlx/__init__.py,sha256=4Sdn8HN-Hf8v0_wCt60DCckCg8BvgM3-9r4YVfZycRE,89
143
143
  sqlx/magic.py,sha256=6VBlotgjautjev599tHaTYOfcfOA9m6gV_-P1_Qc4lI,3622
144
- singlestoredb-1.12.1.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
145
- singlestoredb-1.12.1.dist-info/METADATA,sha256=Va-hWQVTc0rFk4y48dBNEVNd1zyFcG_0MkbkrBQ8LQE,5778
146
- singlestoredb-1.12.1.dist-info/WHEEL,sha256=UyMHzmWA0xVqVPKfTiLs2eN3OWWZUl-kQemNbpIqlKo,100
147
- singlestoredb-1.12.1.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
148
- singlestoredb-1.12.1.dist-info/top_level.txt,sha256=lA65Vf4qAMfg_s1oG3LEO90h4t1Z-SPDbRqkevI3bSY,40
149
- singlestoredb-1.12.1.dist-info/RECORD,,
144
+ singlestoredb-1.12.3.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
145
+ singlestoredb-1.12.3.dist-info/METADATA,sha256=f8Qx8NA3HZiI2rGrFk1g8d41iXhAHaiEVww6DMtDixM,5778
146
+ singlestoredb-1.12.3.dist-info/WHEEL,sha256=UyMHzmWA0xVqVPKfTiLs2eN3OWWZUl-kQemNbpIqlKo,100
147
+ singlestoredb-1.12.3.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
148
+ singlestoredb-1.12.3.dist-info/top_level.txt,sha256=lA65Vf4qAMfg_s1oG3LEO90h4t1Z-SPDbRqkevI3bSY,40
149
+ singlestoredb-1.12.3.dist-info/RECORD,,