singlestoredb 1.7.2__cp38-abi3-win_amd64.whl → 1.9.0__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.

@@ -226,6 +226,8 @@ class Connection(BaseConnection):
226
226
  Set to true to check the server certificate's validity.
227
227
  ssl_verify_identity : bool, optional
228
228
  Set to true to check the server's identity.
229
+ tls_sni_servername: str, optional
230
+ Set server host name for TLS connection
229
231
  read_default_group : str, optional
230
232
  Group to read from in the configuration file.
231
233
  autocommit : bool, optional
@@ -295,6 +297,7 @@ class Connection(BaseConnection):
295
297
  _auth_plugin_name = ''
296
298
  _closed = False
297
299
  _secure = False
300
+ _tls_sni_servername = None
298
301
 
299
302
  def __init__( # noqa: C901
300
303
  self,
@@ -335,6 +338,7 @@ class Connection(BaseConnection):
335
338
  ssl_key=None,
336
339
  ssl_verify_cert=None,
337
340
  ssl_verify_identity=None,
341
+ tls_sni_servername=None,
338
342
  parse_json=True,
339
343
  invalid_values=None,
340
344
  pure_python=None,
@@ -638,6 +642,7 @@ class Connection(BaseConnection):
638
642
 
639
643
  self._is_committable = True
640
644
  self._in_sync = False
645
+ self._tls_sni_servername = tls_sni_servername
641
646
  self._track_env = bool(track_env) or self.host == 'singlestore.com'
642
647
  self._enable_extended_data_types = enable_extended_data_types
643
648
  if vector_data_format.lower() in ['json', 'binary']:
@@ -1364,7 +1369,10 @@ class Connection(BaseConnection):
1364
1369
  if self.ssl and self.server_capabilities & CLIENT.SSL:
1365
1370
  self.write_packet(data_init)
1366
1371
 
1367
- self._sock = self.ctx.wrap_socket(self._sock, server_hostname=self.host)
1372
+ hostname = self.host
1373
+ if self._tls_sni_servername:
1374
+ hostname = self._tls_sni_servername
1375
+ self._sock = self.ctx.wrap_socket(self._sock, server_hostname=hostname)
1368
1376
  self._rfile = self._sock.makefile('rb')
1369
1377
  self._secure = True
1370
1378
 
singlestoredb/py.typed ADDED
File without changes
@@ -0,0 +1,18 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "Test Notebook"
8
+ ]
9
+ }
10
+ ],
11
+ "metadata": {
12
+ "language_info": {
13
+ "name": "python"
14
+ }
15
+ },
16
+ "nbformat": 4,
17
+ "nbformat_minor": 2
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "Test Notebook 2"
8
+ ]
9
+ }
10
+ ],
11
+ "metadata": {
12
+ "language_info": {
13
+ "name": "python"
14
+ }
15
+ },
16
+ "nbformat": 4,
17
+ "nbformat_minor": 2
18
+ }
@@ -465,10 +465,6 @@ class TestWorkspaceFusion(unittest.TestCase):
465
465
  pass
466
466
 
467
467
 
468
- @unittest.skipIf(
469
- os.environ.get('SINGLESTOREDB_FUSION_ENABLE_HIDDEN', '0') == '0',
470
- 'Hidden Fusion commands are not enabled.',
471
- )
472
468
  @pytest.mark.management
473
469
  class TestJobsFusion(unittest.TestCase):
474
470
 
@@ -740,7 +740,7 @@ class TestStage(unittest.TestCase):
740
740
  'rename_test_2/nest_1/nested_rename_test_3.sql', overwrite=True,
741
741
  )
742
742
 
743
- def test_stage_object(self):
743
+ def test_file_object(self):
744
744
  st = self.wg.stage
745
745
 
746
746
  st.mkdir('obj_test')
@@ -1028,3 +1028,275 @@ class TestJob(unittest.TestCase):
1028
1028
  assert deleted
1029
1029
  job = job_manager.get(job.job_id)
1030
1030
  assert job.terminated_at is not None
1031
+
1032
+
1033
+ @pytest.mark.management
1034
+ class TestFileSpaces(unittest.TestCase):
1035
+
1036
+ manager = None
1037
+ personal_space = None
1038
+ shared_space = None
1039
+
1040
+ @classmethod
1041
+ def setUpClass(cls):
1042
+ cls.manager = s2.manage_files()
1043
+ cls.personal_space = cls.manager.personal_space
1044
+ cls.shared_space = cls.manager.shared_space
1045
+
1046
+ @classmethod
1047
+ def tearDownClass(cls):
1048
+ cls.manager = None
1049
+ cls.personal_space = None
1050
+ cls.shared_space = None
1051
+
1052
+ def test_upload_file(self):
1053
+ for space in [self.personal_space, self.shared_space]:
1054
+ root = space.info('/')
1055
+ assert str(root.path) == '/'
1056
+ assert root.type == 'directory'
1057
+
1058
+ # Upload files
1059
+ f = space.upload_file(
1060
+ TEST_DIR / 'test.ipynb',
1061
+ 'upload_test.ipynb',
1062
+ )
1063
+ assert str(f.path) == 'upload_test.ipynb'
1064
+ assert f.type == 'notebook'
1065
+
1066
+ # Download and compare to original
1067
+ txt = f.download(encoding='utf-8')
1068
+ assert txt == open(TEST_DIR / 'test.ipynb').read()
1069
+
1070
+ # Make sure we can't overwrite
1071
+ with self.assertRaises(OSError):
1072
+ space.upload_file(
1073
+ TEST_DIR / 'test.ipynb',
1074
+ 'upload_test.ipynb',
1075
+ )
1076
+
1077
+ # Force overwrite with new content
1078
+ f = space.upload_file(
1079
+ TEST_DIR / 'test2.ipynb',
1080
+ 'upload_test.ipynb', overwrite=True,
1081
+ )
1082
+ assert str(f.path) == 'upload_test.ipynb'
1083
+ assert f.type == 'notebook'
1084
+
1085
+ # Verify new content
1086
+ txt = f.download(encoding='utf-8')
1087
+ assert txt == open(TEST_DIR / 'test2.ipynb').read()
1088
+
1089
+ # Make sure we can't upload a folder
1090
+ with self.assertRaises(s2.ManagementError):
1091
+ space.upload_folder(TEST_DIR, 'test')
1092
+
1093
+ # Cleanup
1094
+ space.remove('upload_test.ipynb')
1095
+
1096
+ def test_open(self):
1097
+ for space in [self.personal_space, self.shared_space]:
1098
+ # See if error is raised for non-existent file
1099
+ with self.assertRaises(s2.ManagementError):
1100
+ space.open('open_test.ipynb', 'r')
1101
+
1102
+ # Load test file
1103
+ space.upload_file(TEST_DIR / 'test.ipynb', 'open_test.ipynb')
1104
+
1105
+ # Read file using `open`
1106
+ with space.open('open_test.ipynb', 'r') as rfile:
1107
+ assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
1108
+
1109
+ # Read file using `open` with 'rt' mode
1110
+ with space.open('open_test.ipynb', 'rt') as rfile:
1111
+ assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
1112
+
1113
+ # Read file using `open` with 'rb' mode
1114
+ with space.open('open_test.ipynb', 'rb') as rfile:
1115
+ assert rfile.read() == open(TEST_DIR / 'test.ipynb', 'rb').read()
1116
+
1117
+ # Read file using `open` with 'rb' mode
1118
+ with self.assertRaises(ValueError):
1119
+ with space.open('open_test.ipynb', 'b') as rfile:
1120
+ pass
1121
+
1122
+ # Attempt overwrite file using `open` with mode 'x'
1123
+ with self.assertRaises(OSError):
1124
+ with space.open('open_test.ipynb', 'x') as wfile:
1125
+ pass
1126
+
1127
+ # Attempt overwrite file using `open` with mode 'w'
1128
+ with space.open('open_test.ipynb', 'w') as wfile:
1129
+ wfile.write(open(TEST_DIR / 'test2.ipynb').read())
1130
+
1131
+ txt = space.download_file('open_test.ipynb', encoding='utf-8')
1132
+
1133
+ assert txt == open(TEST_DIR / 'test2.ipynb').read()
1134
+
1135
+ # Test writer without context manager
1136
+ wfile = space.open('open_raw_test.ipynb', 'w')
1137
+ for line in open(TEST_DIR / 'test.ipynb'):
1138
+ wfile.write(line)
1139
+ wfile.close()
1140
+
1141
+ txt = space.download_file(
1142
+ 'open_raw_test.ipynb',
1143
+ encoding='utf-8',
1144
+ )
1145
+
1146
+ assert txt == open(TEST_DIR / 'test.ipynb').read()
1147
+
1148
+ # Test reader without context manager
1149
+ rfile = space.open('open_raw_test.ipynb', 'r')
1150
+ txt = ''
1151
+ for line in rfile:
1152
+ txt += line
1153
+ rfile.close()
1154
+
1155
+ assert txt == open(TEST_DIR / 'test.ipynb').read()
1156
+
1157
+ # Cleanup
1158
+ space.remove('open_test.ipynb')
1159
+ space.remove('open_raw_test.ipynb')
1160
+
1161
+ def test_obj_open(self):
1162
+ for space in [self.personal_space, self.shared_space]:
1163
+ # Load test file
1164
+ f = space.upload_file(
1165
+ TEST_DIR / 'test.ipynb',
1166
+ 'obj_open_test.ipynb',
1167
+ )
1168
+
1169
+ # Read file using `open`
1170
+ with f.open() as rfile:
1171
+ assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
1172
+
1173
+ # Make sure directories error out
1174
+ with self.assertRaises(s2.ManagementError):
1175
+ space.mkdir('obj_open_dir')
1176
+
1177
+ # Write file using `open`
1178
+ with f.open('w', encoding='utf-8') as wfile:
1179
+ wfile.write(open(TEST_DIR / 'test2.ipynb').read())
1180
+
1181
+ assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.ipynb').read()
1182
+
1183
+ # Test writer without context manager
1184
+ wfile = f.open('w')
1185
+ for line in open(TEST_DIR / 'test.ipynb'):
1186
+ wfile.write(line)
1187
+ wfile.close()
1188
+
1189
+ txt = space.download_file(f.path, encoding='utf-8')
1190
+
1191
+ assert txt == open(TEST_DIR / 'test.ipynb').read()
1192
+
1193
+ # Test reader without context manager
1194
+ rfile = f.open('r')
1195
+ txt = ''
1196
+ for line in rfile:
1197
+ txt += line
1198
+ rfile.close()
1199
+
1200
+ assert txt == open(TEST_DIR / 'test.ipynb').read()
1201
+
1202
+ # Cleanup
1203
+ space.remove('obj_open_test.ipynb')
1204
+
1205
+ def test_os_directories(self):
1206
+ for space in [self.personal_space, self.shared_space]:
1207
+ # Make sure directories error out
1208
+ with self.assertRaises(s2.ManagementError):
1209
+ space.mkdir('mkdir_test_1')
1210
+
1211
+ with self.assertRaises(s2.ManagementError):
1212
+ space.exists('mkdir_test_1/')
1213
+
1214
+ out = space.listdir('/')
1215
+ assert 'mkdir_test_1/' not in out
1216
+
1217
+ with self.assertRaises(s2.ManagementError):
1218
+ space.rmdir('mkdir_test_1/')
1219
+
1220
+ def test_os_rename(self):
1221
+ for space in [self.personal_space, self.shared_space]:
1222
+ space.upload_file(
1223
+ TEST_DIR / 'test.ipynb',
1224
+ 'rename_test.ipynb',
1225
+ )
1226
+ assert 'rename_test.ipynb' in space.listdir('/')
1227
+ assert 'rename_test_2.ipynb' not in space.listdir('/')
1228
+
1229
+ space.rename(
1230
+ 'rename_test.ipynb',
1231
+ 'rename_test_2.ipynb',
1232
+ )
1233
+ assert 'rename_test.ipynb' not in space.listdir('/')
1234
+ assert 'rename_test_2.ipynb' in space.listdir('/')
1235
+
1236
+ # non-existent file
1237
+ with self.assertRaises(OSError):
1238
+ space.rename('rename_foo.ipynb', 'rename_foo_2.ipynb')
1239
+
1240
+ space.upload_file(
1241
+ TEST_DIR / 'test.ipynb',
1242
+ 'rename_test_3.ipynb',
1243
+ )
1244
+
1245
+ # overwrite
1246
+ with self.assertRaises(OSError):
1247
+ space.rename(
1248
+ 'rename_test_2.ipynb',
1249
+ 'rename_test_3.ipynb',
1250
+ )
1251
+
1252
+ space.rename(
1253
+ 'rename_test_2.ipynb',
1254
+ 'rename_test_3.ipynb', overwrite=True,
1255
+ )
1256
+
1257
+ # Cleanup
1258
+ space.remove('rename_test_3.ipynb')
1259
+
1260
+ def test_file_object(self):
1261
+ for space in [self.personal_space, self.shared_space]:
1262
+ f = space.upload_file(
1263
+ TEST_DIR / 'test.ipynb',
1264
+ 'obj_test.ipynb',
1265
+ )
1266
+
1267
+ assert not f.is_dir()
1268
+ assert f.is_file()
1269
+
1270
+ # abspath / basename / dirname / exists
1271
+ assert f.abspath() == 'obj_test.ipynb'
1272
+ assert f.basename() == 'obj_test.ipynb'
1273
+ assert f.dirname() == '/'
1274
+ assert f.exists()
1275
+
1276
+ # download
1277
+ assert f.download(encoding='utf-8') == \
1278
+ open(TEST_DIR / 'test.ipynb', 'r').read()
1279
+ assert f.download() == open(TEST_DIR / 'test.ipynb', 'rb').read()
1280
+
1281
+ assert space.is_file('obj_test.ipynb')
1282
+ f.remove()
1283
+ assert not space.is_file('obj_test.ipynb')
1284
+
1285
+ # mtime / ctime
1286
+ assert f.getmtime() > 0
1287
+ assert f.getctime() > 0
1288
+
1289
+ # rename
1290
+ f = space.upload_file(
1291
+ TEST_DIR / 'test.ipynb',
1292
+ 'obj_test.ipynb',
1293
+ )
1294
+ assert space.exists('obj_test.ipynb')
1295
+ assert not space.exists('obj_test_2.ipynb')
1296
+ f.rename('obj_test_2.ipynb')
1297
+ assert not space.exists('obj_test.ipynb')
1298
+ assert space.exists('obj_test_2.ipynb')
1299
+ assert f.abspath() == 'obj_test_2.ipynb'
1300
+
1301
+ # Cleanup
1302
+ space.remove('obj_test_2.ipynb')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.7.2
3
+ Version: 1.9.0
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,10 +1,11 @@
1
- _singlestoredb_accel.pyd,sha256=74y_1TpqSVFQ-rTN7XE73b1dJ16kA-s1K57dV-NmkEY,59392
2
- singlestoredb/__init__.py,sha256=jSf2uwMGpvrTEf60dTZMxvO63yzzmKGE12bZUuCMOQA,1697
1
+ _singlestoredb_accel.pyd,sha256=garYUCMiztjRATI49oXgFrKMrTJfMkYHoT9CsdRVVTw,59392
2
+ singlestoredb/__init__.py,sha256=uPTzIZk0aLMH0g8uIDrCqusEFDP5oLCegTc5h6XwM3U,1711
3
3
  singlestoredb/auth.py,sha256=RmYiH0Wlc2RXc4pTlRMysxtBI445ggCIwojWKC_eDLE,7844
4
- singlestoredb/config.py,sha256=LlrwKor_23uA9u7jWBYb-IaOKs30CjWIM7o9xCXEPcc,12721
5
- singlestoredb/connection.py,sha256=QC5YQemwJOhdW_-ZBFpNLE15xFxwI1fB2LuvE1hNi9k,46812
4
+ singlestoredb/config.py,sha256=n6ludREIoiZDEzSGmv0xouv_zHFnznKNKxSvjzgQ3Lk,12876
5
+ singlestoredb/connection.py,sha256=I3A0VkA_E6pfUNCzIvj0KTb5zn6-htBAPsu6OVXO9-I,47150
6
6
  singlestoredb/converters.py,sha256=7_Of1f3Ow-JUoY1pHFlMPYxvt8llzdk-7X8qk5z2xJM,21631
7
7
  singlestoredb/exceptions.py,sha256=WCCJrNSsU-hD-621Jpd6bwmvGftQ7byXkk-XKXlaxpg,3354
8
+ singlestoredb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
9
  singlestoredb/pytest.py,sha256=TH364xRCN7_QaN0oRQDHixrEcDx_ZBgu3bmY0tvKrYU,9357
9
10
  singlestoredb/types.py,sha256=Lv0BEQl6aSZBiAe0OSI07FEJhcHZ9HX45iT9NU_mxHQ,10334
10
11
  singlestoredb/ai/__init__.py,sha256=nT048t90xqjaNhz7KJ10KfSVW4RcZRoujyC6po6Nmb8,61
@@ -31,29 +32,33 @@ singlestoredb/functions/ext/rowdat_1.py,sha256=KYj_y5JWm3_B2-QC47HK-CNOrzujBqGUw
31
32
  singlestoredb/functions/ext/utils.py,sha256=OPMFD-tTCx2Kk9jguQkrTr7e4AgNkt15YsvaT1YSmN8,5480
32
33
  singlestoredb/fusion/__init__.py,sha256=FHWtrg6OJFTf6Ye197V5sU6ssryr2h6FBcDIgXP7-H4,367
33
34
  singlestoredb/fusion/graphql.py,sha256=SHqsPe4xgawdsTPHEtJGQlybYGWqPrGMmyK-v20RLac,5420
34
- singlestoredb/fusion/handler.py,sha256=iILH6aeVWOPbqbkJ8m8CVYV61uJnw8ohYIv8U7bXklM,26231
35
+ singlestoredb/fusion/handler.py,sha256=dZ5goR9aseeGbSRTVZpxIYeLsraWE5VTNtA7nskt3rk,28507
35
36
  singlestoredb/fusion/registry.py,sha256=_eT1gd38VPlFKs5f9Pu6lqQyoDQ_ixW5O56QwYLQ89Y,6361
36
37
  singlestoredb/fusion/result.py,sha256=EcFY5Qv43ySlQsfl_JB-I3ko7PzVdjuhhoKN96uHSAM,12171
37
38
  singlestoredb/fusion/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- singlestoredb/fusion/handlers/job.py,sha256=TE57c3IDsKb6C5QH4AKAZBMMeZCIaQ4NrZPzXIq0KaI,21959
39
- singlestoredb/fusion/handlers/stage.py,sha256=uPqawMvchnpyrPYLhB0joTremCURNYKOvYntFc3zTRU,14133
40
- singlestoredb/fusion/handlers/utils.py,sha256=7xWb_1mJzxW0po9iHVY2ZVnRvHIQgOlKZQZ1zllJBjk,5271
39
+ singlestoredb/fusion/handlers/export.py,sha256=mY-lEbi3WZLEHJ91hAFPYcasP4g1VeQkP0xBj3casts,7045
40
+ singlestoredb/fusion/handlers/files.py,sha256=sXO5OFS5QsmwNOnMY3G5xpMPqh4_WkssW3dOB6uI4VQ,19669
41
+ singlestoredb/fusion/handlers/job.py,sha256=3enfxHwERH7T4u0FEwOPN0IL0GtepaCYgEsisiy3Df4,21753
42
+ singlestoredb/fusion/handlers/stage.py,sha256=PP-SSP204lwpmnycSXXSmFPzoN535JVuwglDCbaQ8Lw,14789
43
+ singlestoredb/fusion/handlers/utils.py,sha256=OffWWj8FkZgqr5ijY3WQ6cwaAB7a7Z5cYxLeOAfU_JE,10356
41
44
  singlestoredb/fusion/handlers/workspace.py,sha256=NxoEY5xd5lCQmXiim4nhAYCL0agHo1H_rGPpqa31hiw,28397
42
45
  singlestoredb/http/__init__.py,sha256=4cEDvLloGc3LSpU-PnIwacyu0n5oIIIE6xk2SPyWD_w,939
43
46
  singlestoredb/http/connection.py,sha256=LFUeWx7maS7xhQLqEX3pgvIGoosqyJTovtWwJ1DyDSA,40721
44
- singlestoredb/management/__init__.py,sha256=pQbsOffl-i6zIzR63MCxSjxPY6TNmy7Kg0BCTExt3mk,244
47
+ singlestoredb/management/__init__.py,sha256=A66ZnFyX--PsAZ2tvtYUfIUBvVGDBFQsnVc6nGTlX60,277
45
48
  singlestoredb/management/billing_usage.py,sha256=0UHFSPCrN0nyeGFFM-HXS3NP8pYmYo2BCCahDEPXvzg,3883
46
49
  singlestoredb/management/cluster.py,sha256=XfdBuTlrAG-mnW1BFKeoAr4YSE5IVgxLjbuBSpqIySo,14823
50
+ singlestoredb/management/export.py,sha256=Ksrb8_sxeqva4NElaGxdPQUWQga2yOEkf0wnw5M-iGc,4302
51
+ singlestoredb/management/files.py,sha256=PmXEeJuIYthGJpjgTaGmb5eMD1lIfJSevOVnf8QDXYk,29204
47
52
  singlestoredb/management/job.py,sha256=Npfe1JLYJlggGBrXLniPKwKUKF1i3alvSY1SFtvauSs,25498
48
53
  singlestoredb/management/manager.py,sha256=uGNrUe3zhuP-HUqdfwvy4MdEXTCmq-FZKjIwZSc3hOM,9096
49
54
  singlestoredb/management/organization.py,sha256=JBsNC4R3boUKdYvyCZyfGoVMC1mD6SPuMI1UssBVoOM,5611
50
55
  singlestoredb/management/region.py,sha256=oGoLLS88dE1GmY7GCc0BV7X3f7bWwKQyeXOVBFmK9Pk,1678
51
56
  singlestoredb/management/utils.py,sha256=BP-Wb8Sg16GbdLI_DeBz-3ttMklz6ZjYyMOz-sxElz8,13594
52
- singlestoredb/management/workspace.py,sha256=BBNa3Af5IaUAwtzHWrNTJXoc8anXJ7FiWH9yIvtg79Q,63610
57
+ singlestoredb/management/workspace.py,sha256=EKSmu7sFBgotcgk0zlQP1Ke6ZnfQxwl_4tY7hev1yO4,58018
53
58
  singlestoredb/mysql/__init__.py,sha256=CbpwzNUJPAmKPpIobC0-ugBta_RgHCMq7X7N75QLReY,4669
54
59
  singlestoredb/mysql/_auth.py,sha256=YaqqyvAHmeraBv3BM207rNveUVPM-mPnW20ts_ynVWg,8341
55
60
  singlestoredb/mysql/charset.py,sha256=mnCdMpvdub1S2mm2PSk2j5JddgsWRjsVLtGx-y9TskE,10724
56
- singlestoredb/mysql/connection.py,sha256=M7SH2QJbD6IG-E6UOzLuneGtXKmKbvjhvYn18EMnA_A,74379
61
+ singlestoredb/mysql/connection.py,sha256=mmTF9X7cPbmTS7yPHHbkjwQGpOPSbpDgZ3MNd2MyXkA,74716
57
62
  singlestoredb/mysql/converters.py,sha256=vebFFm6IrC0WgY-5Eh-esaPizY5cq3vDOUlEKGaYM-U,7771
58
63
  singlestoredb/mysql/cursors.py,sha256=pkrP-1t8IhBJRnYpdM7Rdm-332nOq1RYTDJ_yg_q5HI,27682
59
64
  singlestoredb/mysql/err.py,sha256=aDbmfq08gWVmfgIea735wSeiFdvYbB5wusgd3qTVq1s,2480
@@ -97,7 +102,9 @@ singlestoredb/notebook/_portal.py,sha256=ip3MkJ51syxppUVGRjgqLfR0He4KCZVklFwWT_X
97
102
  singlestoredb/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
103
  singlestoredb/tests/empty.sql,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
104
  singlestoredb/tests/local_infile.csv,sha256=0fYxcZcTvcwS2McF4sktFsipRY1G-ClGmCRR1eCqdEQ,45
105
+ singlestoredb/tests/test.ipynb,sha256=IEgXbByXyWDplZvod1J2SqNHZiPOGdD4oNVMd0ArP7s,234
100
106
  singlestoredb/tests/test.sql,sha256=winJzhZ2W52PcQ1j8X_NNIDRBmEa-xMAYrS_hoUtAP8,18337
107
+ singlestoredb/tests/test2.ipynb,sha256=_kBQVvEoinQ1zInNcWFKpbdw-djkLsEO8l3g2MEU_Zs,236
101
108
  singlestoredb/tests/test2.sql,sha256=CEM8_lX189iQU65G3Pod7lig6osfrveQyoDz6HC35YQ,38
102
109
  singlestoredb/tests/test_basics.py,sha256=tLiR46qUy8-OHHRSHsQTBp5q9NAwNPR9HvfutI6YgnM,47629
103
110
  singlestoredb/tests/test_config.py,sha256=Ad0PDmCnJMOyy9f7WTKiRasSR_3mYRByUlSb7k5ZySg,11502
@@ -106,9 +113,9 @@ singlestoredb/tests/test_dbapi.py,sha256=cNJoTEZvYG7ckcwT7xqlkJX-2TDEYGTDDU1Iguc
106
113
  singlestoredb/tests/test_exceptions.py,sha256=vscMYmdOJr0JmkTAJrNI2w0Q96Nfugjkrt5_lYnw8i0,1176
107
114
  singlestoredb/tests/test_ext_func.py,sha256=gQErR-wAN8BqLNG5U4pNbg4qkQEo6Re8Hd9_Ztqo1RM,38550
108
115
  singlestoredb/tests/test_ext_func_data.py,sha256=9kn8BWmCjkbnP6hSbFhmhcdW4OmVT-GSvBTIzFBLEys,48796
109
- singlestoredb/tests/test_fusion.py,sha256=Qab2TTVwUPo7NPso6nIQgDkR6hHtDLp5j1gO7X4bDsk,24685
116
+ singlestoredb/tests/test_fusion.py,sha256=ckATjXKcDBvej68PZgTRJirdoywtUJ7WXkZmfR0la_4,24544
110
117
  singlestoredb/tests/test_http.py,sha256=7hwXe61hlUes3nji0MTTZweo94tJAlJ-vA5ct9geXFQ,8868
111
- singlestoredb/tests/test_management.py,sha256=B4NkK7J0luuS7T-7OR5qzu-v8gkViIiXie-58bHQIDQ,35334
118
+ singlestoredb/tests/test_management.py,sha256=y6K9dm-s9wxi1yk7fGt-h6hYlB9W4VZy4FgxfVbgZcE,44782
112
119
  singlestoredb/tests/test_plugin.py,sha256=P1nXLnTafaHkHN-6bVbGryxTu7OWJPU9SYFZ_WQUwq8,845
113
120
  singlestoredb/tests/test_results.py,sha256=Zg1ynZFRZqalAMfNLOU5C6BDXaox6JxrKm_XZwVNFcg,6753
114
121
  singlestoredb/tests/test_types.py,sha256=YeVE6KPqlqzJke-4hbRmc8ko1E7RLHu5S8qLg04Bl5Y,4632
@@ -125,9 +132,11 @@ singlestoredb/utils/events.py,sha256=rC9cHAetua_E1f-EiFkFM-gJzQSQIH5Uk-4sspC3KjI
125
132
  singlestoredb/utils/mogrify.py,sha256=gCcn99-vgsGVjTUV7RHJ6hH4vCNrsGB_Xo4z8kiSPDQ,4201
126
133
  singlestoredb/utils/results.py,sha256=wR70LhCqlobniZf52r67zYLBOKjWHQm68NAskdRQND8,15862
127
134
  singlestoredb/utils/xdict.py,sha256=-wi1lSPTnY99fhVMBhPKJ8cCsQhNG4GMUfkEBDKYgCw,13321
128
- singlestoredb-1.7.2.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
129
- singlestoredb-1.7.2.dist-info/METADATA,sha256=EcxKSeGY473TFRuGP6W42122Mz_hry8hPLXAVseir0A,5710
130
- singlestoredb-1.7.2.dist-info/WHEEL,sha256=UyMHzmWA0xVqVPKfTiLs2eN3OWWZUl-kQemNbpIqlKo,100
131
- singlestoredb-1.7.2.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
132
- singlestoredb-1.7.2.dist-info/top_level.txt,sha256=SDtemIXf-Kp-_F2f_S6x0db33cHGOILdAEsIQZe2LZc,35
133
- singlestoredb-1.7.2.dist-info/RECORD,,
135
+ sqlx/__init__.py,sha256=4Sdn8HN-Hf8v0_wCt60DCckCg8BvgM3-9r4YVfZycRE,89
136
+ sqlx/magic.py,sha256=6VBlotgjautjev599tHaTYOfcfOA9m6gV_-P1_Qc4lI,3622
137
+ singlestoredb-1.9.0.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
138
+ singlestoredb-1.9.0.dist-info/METADATA,sha256=-R-w0V56XC6oKl6i-EgytHVnL1Z1_vhuJNG4aLAnj5o,5710
139
+ singlestoredb-1.9.0.dist-info/WHEEL,sha256=UyMHzmWA0xVqVPKfTiLs2eN3OWWZUl-kQemNbpIqlKo,100
140
+ singlestoredb-1.9.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
141
+ singlestoredb-1.9.0.dist-info/top_level.txt,sha256=lA65Vf4qAMfg_s1oG3LEO90h4t1Z-SPDbRqkevI3bSY,40
142
+ singlestoredb-1.9.0.dist-info/RECORD,,
@@ -1,2 +1,3 @@
1
1
  _singlestoredb_accel
2
2
  singlestoredb
3
+ sqlx
sqlx/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from sqlx.magic import load_ipython_extension
2
+
3
+
4
+ __all__ = ['load_ipython_extension']
sqlx/magic.py ADDED
@@ -0,0 +1,113 @@
1
+ import os
2
+ from typing import Any
3
+ from typing import Optional
4
+
5
+ from IPython.core.interactiveshell import InteractiveShell
6
+ from IPython.core.magic import cell_magic
7
+ from IPython.core.magic import line_magic
8
+ from IPython.core.magic import Magics
9
+ from IPython.core.magic import magics_class
10
+ from IPython.core.magic import needs_local_scope
11
+ from IPython.core.magic import no_var_expand
12
+ from sql.magic import SqlMagic
13
+ from sqlalchemy import create_engine
14
+ from sqlalchemy import Engine
15
+ from sqlalchemy import PoolProxiedConnection
16
+
17
+ DEFAULT_POOL_SIZE = 10 # Maximum number of connections in the pool
18
+ DEFAULT_MAX_OVERFLOW = 5 # additional connections (temporary overflow)
19
+ DEFAULT_POOL_TIMEOUT = 30 # Wait time for a connection from the pool
20
+
21
+
22
+ @magics_class
23
+ class SqlxMagic(Magics):
24
+ def __init__(self, shell: InteractiveShell):
25
+ Magics.__init__(self, shell=shell)
26
+ self.magic = SqlMagic(shell)
27
+ self.engine: Optional['Engine'] = None
28
+
29
+ @no_var_expand
30
+ @needs_local_scope
31
+ @line_magic('sqlx')
32
+ @cell_magic('sqlx')
33
+ def sqlx(self, line: str, cell: str = '', local_ns: Any = None) -> Any:
34
+ """
35
+ Runs SQL statement against a database, specified by
36
+ SQLAlchemy connect string present in DATABASE_URL environment variable.
37
+
38
+ The magic can be used both as a cell magic `%%sqlx` and
39
+ line magic `%sqlx` (see examples below).
40
+
41
+ This is a thin wrapper around the [jupysql](https://jupysql.ploomber.io/) magic,
42
+ allowing multi-threaded execution.
43
+ A connection pool will be maintained internally.
44
+
45
+ Examples::
46
+
47
+ # Line usage
48
+
49
+ %sqlx SELECT * FROM mytable
50
+
51
+ result = %sqlx SELECT 1
52
+
53
+
54
+ # Cell usage
55
+
56
+ %%sqlx
57
+ DELETE FROM mytable
58
+
59
+ %%sqlx
60
+ DROP TABLE mytable
61
+
62
+ """
63
+
64
+ connection = self.get_connection()
65
+ try:
66
+ result = self.magic.execute(line, cell, local_ns, connection)
67
+ finally:
68
+ connection.close()
69
+
70
+ return result
71
+
72
+ def get_connection(self) -> PoolProxiedConnection:
73
+ if self.engine is None:
74
+ if 'DATABASE_URL' not in os.environ:
75
+ raise RuntimeError(
76
+ 'Cannot create connection pool, environment variable'
77
+ " 'DATABASE_URL' is missing.",
78
+ )
79
+
80
+ # TODO: allow configuring engine
81
+ # idea: %sqlx engine
82
+ # idea: %%sqlx engine
83
+ self.engine = create_engine(
84
+ os.environ['DATABASE_URL'],
85
+ pool_size=DEFAULT_POOL_SIZE,
86
+ max_overflow=DEFAULT_MAX_OVERFLOW,
87
+ pool_timeout=DEFAULT_POOL_TIMEOUT,
88
+ )
89
+
90
+ return self.engine.raw_connection()
91
+
92
+
93
+ # In order to actually use these magics, you must register them with a
94
+ # running IPython.
95
+
96
+
97
+ def load_ipython_extension(ip: InteractiveShell) -> None:
98
+ """
99
+ Any module file that define a function named `load_ipython_extension`
100
+ can be loaded via `%load_ext module.path` or be configured to be
101
+ autoloaded by IPython at startup time.
102
+ """
103
+
104
+ # Load jupysql extension
105
+ # This is necessary for jupysql to initialize internal state
106
+ # required to render messages
107
+ assert ip.extension_manager is not None
108
+ result = ip.extension_manager.load_extension('sql')
109
+ if result == 'no load function':
110
+ raise RuntimeError('Could not load sql extension. Is jupysql installed?')
111
+
112
+ # Register sqlx
113
+ ip.register_magics(SqlxMagic(ip))