singlestoredb 1.8.0__py3-none-any.whl → 1.9.0__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.

@@ -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
 
@@ -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
+ }
@@ -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.8.0
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,7 +1,7 @@
1
- singlestoredb/__init__.py,sha256=Kvu0WMBtP9TgYwY6eu7vuBfMfx-sbawDTrZDGewMCuo,1634
1
+ singlestoredb/__init__.py,sha256=2NT8djBgcovcYu3ttVecdDhhDZit6vnMyAtm_HsFuHQ,1648
2
2
  singlestoredb/auth.py,sha256=u8D9tpKzrqa4ssaHjyZnGDX1q8XBpGtuoOkTkSv7B28,7599
3
- singlestoredb/config.py,sha256=NtONv4Etpraoy1nenHqRAS08xHJZmho00J95uDjLxQM,12290
4
- singlestoredb/connection.py,sha256=x5lINBa9kB_GoEEeL2uUZi9G8pNwKuFtA1uqJirR6HI,45352
3
+ singlestoredb/config.py,sha256=ZGmnG37Ug1hT5jdTSdMJBCGDvvz2GwxrZZ_ROVV2wUE,12439
4
+ singlestoredb/connection.py,sha256=PlOD-4Qx3Q-lVPPdjru-kTC8kOWDos3eiWVo7pPKeKM,45679
5
5
  singlestoredb/converters.py,sha256=t1hRMZfccWJs_WyOw-W-Kh87fxsOkpOnKXAeh_Nr-zU,20681
6
6
  singlestoredb/exceptions.py,sha256=HuoA6sMRL5qiCiee-_5ddTGmFbYC9Euk8TYUsh5GvTw,3234
7
7
  singlestoredb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -31,31 +31,33 @@ singlestoredb/functions/ext/rowdat_1.py,sha256=JgKRsVSQYczFD6cmo2xLilbNPYpyLL2tP
31
31
  singlestoredb/functions/ext/utils.py,sha256=2-B8YU_Iekv8JcpI-ochs9TIeuyatLaLAH-AyYyUUIg,5311
32
32
  singlestoredb/fusion/__init__.py,sha256=Qo7SuqGw-l-vE8-EI2jhm6hXJkYfOLUKIws9c7LFNX0,356
33
33
  singlestoredb/fusion/graphql.py,sha256=ZA3HcDq5rER-dCEavwTqnF7KM0D2LCYIY7nLQk7lSso,5207
34
- singlestoredb/fusion/handler.py,sha256=ohoGzWLJ-t8co08FUmpe-2ELh5eFY5bLJw7rgxXslso,27540
34
+ singlestoredb/fusion/handler.py,sha256=5DjY8bUf8x-d0kVr9mba_o0W5vLRFj5ImKk-on_UtYk,27607
35
35
  singlestoredb/fusion/registry.py,sha256=jjdRTYZ3ylhy6gAoW5xBj0tkxGFBT-2yLQ0tztTgDIY,6112
36
36
  singlestoredb/fusion/result.py,sha256=Bd3KbRpqWqQcWp_Chd4bzBy8Kfc8nXLS_Pn_GGbPO6o,11772
37
37
  singlestoredb/fusion/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- singlestoredb/fusion/handlers/export.py,sha256=jzPD6b7oTTWYMt25bFNxZVaw6PUfrKpAk-VjvrOQomQ,7097
38
+ singlestoredb/fusion/handlers/export.py,sha256=GJ-tykXHK0iMNAvJrJvtrGCgRpRtlFwFt-e7hxKhp2U,6808
39
+ singlestoredb/fusion/handlers/files.py,sha256=c-QaWzgLGRMheFvG2uUeYfVgLA5WfSE3e9XeBrsFNh0,18979
39
40
  singlestoredb/fusion/handlers/job.py,sha256=r0KdOD55VUDw-SymC__5Mn-fzJTZE_xcBgH-O8DYVHc,21095
40
- singlestoredb/fusion/handlers/stage.py,sha256=4BSwqcmA4PI3oksTdYaTmQ6bY-AzmL0mZfGnP3U0ldM,13643
41
- singlestoredb/fusion/handlers/utils.py,sha256=oYbf13Y3orEkJfHMNnO7B_W1anEdK-0S9vVVkF2pPFk,5109
41
+ singlestoredb/fusion/handlers/stage.py,sha256=kYVjbPys83kf3jX6jWwN8Ju0oEocKVZ3TIOt2HiC5Ew,14287
42
+ singlestoredb/fusion/handlers/utils.py,sha256=yXggiwnKq7IrCFXQwY-ZNc8gHxvftUUq4NXc8LCP7eM,10046
42
43
  singlestoredb/fusion/handlers/workspace.py,sha256=4xN2TFO4yF7KZB2Fcht7IuvoDdAT6fDfDLjixiHZN8w,27506
43
44
  singlestoredb/http/__init__.py,sha256=A_2ZUCCpvRYIA6YDpPy57wL5R1eZ5SfP6I1To5nfJ2s,912
44
45
  singlestoredb/http/connection.py,sha256=dU0a72pMpyq9l9ADKs5jpB-GAJScBxgd83NOlGreIdc,39473
45
- singlestoredb/management/__init__.py,sha256=mhWXjLhp5-t8dhl0vl7SjayKrvJlDb5_hl1YWvDgiMA,237
46
+ singlestoredb/management/__init__.py,sha256=ofNTPCdkZ1dS_aX2aUujd8aMHQi8Lle5Ced0aaO3RH4,269
46
47
  singlestoredb/management/billing_usage.py,sha256=9ighjIpcopgIyJOktBYQ6pahBZmWGHOPyyCW4gu9FGs,3735
47
48
  singlestoredb/management/cluster.py,sha256=i23Smr1PBrDZ8NO_VPd_-bEYkyHvVe9CCRGUjHn_1yQ,14362
48
- singlestoredb/management/export.py,sha256=jAqpoyKHyT9rPZhxn1OQdq4l7U3V-RG0074XiCyjMZc,8658
49
+ singlestoredb/management/export.py,sha256=_kr5grRVFe0RGuk_XoDGYq_4VXMvGCWvkan9dZO-jGA,4156
50
+ singlestoredb/management/files.py,sha256=AwqcYn1x0uWKzV_RYQgcpSRcMblPP5jKvgcmesyCP00,28166
49
51
  singlestoredb/management/job.py,sha256=4-xLWzbE8odQogVVaFer80UEoTAZY1T28VZ9Ug4rbmM,24611
50
52
  singlestoredb/management/manager.py,sha256=sFP1vZGS8WpN8E0XLu1N7Mxtq6Sixalln44HlTQEyXI,8800
51
53
  singlestoredb/management/organization.py,sha256=hqMaM7H-naMjNbxDl_f7G_2o5TkiGKyzPhxuzDveJAw,5402
52
54
  singlestoredb/management/region.py,sha256=HnLcWUh7r_aLECliplCDHak4a_F3B7LOSXEYMW66qD0,1611
53
55
  singlestoredb/management/utils.py,sha256=P4fp8a7EwaYiag_hvpILcgwXtdFNYKKO70dsKjmxn1A,13171
54
- singlestoredb/management/workspace.py,sha256=9oamNIaE5vLZNAlpl5SK-xu27qPqx2Ff3ZIdKckNfmc,61673
56
+ singlestoredb/management/workspace.py,sha256=MdVuCxNv6C1QgiT-1V8kqZeWLeOYWnnDSYMUvj7EtRU,56268
55
57
  singlestoredb/mysql/__init__.py,sha256=olUTAvkiERhDW41JXQMawkg-i0tvBEkoTkII1tt6lxU,4492
56
58
  singlestoredb/mysql/_auth.py,sha256=AugRitoUwgRIDFuJxuAH4MWIAmckY7Ji2pP6r_Ng9dY,8043
57
59
  singlestoredb/mysql/charset.py,sha256=-FlONDS_oAUF5B3mIgeHBPb_SCt4zHD33arUeBNctU0,10510
58
- singlestoredb/mysql/connection.py,sha256=6N7ZG3rNMrpQhv4eixA686NdJ5GBe4HknSpy46qlZ9g,72367
60
+ singlestoredb/mysql/connection.py,sha256=MfPjQfAadI8xtn4xbaock34bCCJ2P1uaTqAw8o1PqS8,72696
59
61
  singlestoredb/mysql/converters.py,sha256=CVe8SDmjbIAhy1xpQ2N5OKWw6t5eWpw-EU3QTlA0Hh0,7500
60
62
  singlestoredb/mysql/cursors.py,sha256=Eqe7jITRvOo4P_TxIarTumg_2PG1DcCfZ4Uo9IFdDa8,26794
61
63
  singlestoredb/mysql/err.py,sha256=-m5rqXi8yhq6b8SCEJ2h0E5Rudh_15dlAU_WbJ1YrM8,2388
@@ -99,7 +101,9 @@ singlestoredb/notebook/_portal.py,sha256=DLerIEQmAUymtYcx8RBeuYJ4pJSy_xl1K6t1Oc-
99
101
  singlestoredb/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
102
  singlestoredb/tests/empty.sql,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
103
  singlestoredb/tests/local_infile.csv,sha256=sBtqjvfkS9aoOVx8nMXYgYv4rDuT4OuYhqUhNRu0O68,42
104
+ singlestoredb/tests/test.ipynb,sha256=jrkI2WoSsUA9xQpKTBCHnsDptryQhPdM5QaxfvYRGpg,216
102
105
  singlestoredb/tests/test.sql,sha256=dfMehVCQ9wObSVTQKyQi-fRFDZeqRxV4Cj8doBCPEFM,17679
106
+ singlestoredb/tests/test2.ipynb,sha256=yd1PE1VK-DwiRd6mYS4_0cPBtuVkvcDtycvTwD-YnDo,218
103
107
  singlestoredb/tests/test2.sql,sha256=D4U2GSlOVeo39U8-RMM4YziJzYFfi4Ztm2YXJVJVAS8,37
104
108
  singlestoredb/tests/test_basics.py,sha256=1__lEF7FmQF4_pFi5R53TtJidtQznmQ592Ci6aDVgrc,46368
105
109
  singlestoredb/tests/test_config.py,sha256=63lyIQ2KrvGE6C9403B_4Mc90mX4tp42ys5Bih2sXrE,11184
@@ -110,7 +114,7 @@ singlestoredb/tests/test_ext_func.py,sha256=OWd-CJ1Owhx72nikSWWEF2EQFCJk7vEXZM2O
110
114
  singlestoredb/tests/test_ext_func_data.py,sha256=yTADD93nPxX6_rZXXLZaOWEI_yPvYyir9psn5PK9ctU,47695
111
115
  singlestoredb/tests/test_fusion.py,sha256=W3aRfBeu8HBGm1CIQWFIeWUPBUlfHBCbJy8vejPHdRs,23828
112
116
  singlestoredb/tests/test_http.py,sha256=RXasTqBWRn__omj0eLFTJYIbZjd0PPdIV2d4Cqz0MC8,8580
113
- singlestoredb/tests/test_management.py,sha256=89hKu82qiH1YTbLzKl5FOPEapNB5qo8k06d3fkoYajc,34304
117
+ singlestoredb/tests/test_management.py,sha256=biqwuHfzaH5Jay1yj-nHnX7fhvLB9DPcjujmJR_T1vM,43480
114
118
  singlestoredb/tests/test_plugin.py,sha256=qpO9wmWc62VaijN1sJ97YSYIX7I7Y5C6sY-WzwrutDQ,812
115
119
  singlestoredb/tests/test_results.py,sha256=wg93sujwt-R9_eJCgSCElgAZhLDkIiAo3qPkPydOv78,6582
116
120
  singlestoredb/tests/test_types.py,sha256=jqoAaSjhbgwB3vt0KsTcl7XBWoMMIa0mPFKhEi5bBjo,4500
@@ -127,9 +131,11 @@ singlestoredb/utils/events.py,sha256=9IB84T3pKQjs7aaoSSJCw7soNngnhoTDWIC52M51R9Y
127
131
  singlestoredb/utils/mogrify.py,sha256=-a56IF70U6CkfadeaZgfjRSVsAD3PuqRrzPpjZlgbwY,4050
128
132
  singlestoredb/utils/results.py,sha256=bJtaUaDiFq26IsPAKZ2FHGB7csMn94EAxLKrP4HaEEA,15277
129
133
  singlestoredb/utils/xdict.py,sha256=S9HKgrPrnu_6b7iOwa2KrW8CmU1Uqx0BWdEyogFzWbE,12896
130
- singlestoredb-1.8.0.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
131
- singlestoredb-1.8.0.dist-info/METADATA,sha256=TOVRXyXoPew6AtTrB7b9oM3PBLYtgsb19SEBBKvOeic,5557
132
- singlestoredb-1.8.0.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
133
- singlestoredb-1.8.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
134
- singlestoredb-1.8.0.dist-info/top_level.txt,sha256=eet8bVPNRqiGeY0PrO5ERH2UpamwlrKHEQCffz4dOh8,14
135
- singlestoredb-1.8.0.dist-info/RECORD,,
134
+ sqlx/__init__.py,sha256=aBYiU8DZXCogvWu3yWafOz7bZS5WWwLZXj7oL0dXGyU,85
135
+ sqlx/magic.py,sha256=JsS9_9aBFaOt91Torm1JPN0c8qB2QmYJmNSKtbSQIY0,3509
136
+ singlestoredb-1.9.0.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
137
+ singlestoredb-1.9.0.dist-info/METADATA,sha256=gCoMBtMdsViRJC1mdvHhMjhKd0IleF7zYsg6LqfNSl0,5557
138
+ singlestoredb-1.9.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
139
+ singlestoredb-1.9.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
140
+ singlestoredb-1.9.0.dist-info/top_level.txt,sha256=DfFGz7bM4XrshloiCeTABgylT3BUnS8T5pJam3ewT6Q,19
141
+ singlestoredb-1.9.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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))