singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of singlestoredb might be problematic. Click here for more details.

Files changed (120) hide show
  1. singlestoredb/__init__.py +33 -1
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +5 -1
  4. singlestoredb/config.py +116 -14
  5. singlestoredb/connection.py +483 -516
  6. singlestoredb/converters.py +238 -135
  7. singlestoredb/exceptions.py +30 -2
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/stage.py +257 -0
  22. singlestoredb/fusion/handlers/utils.py +162 -0
  23. singlestoredb/fusion/handlers/workspace.py +412 -0
  24. singlestoredb/fusion/registry.py +164 -0
  25. singlestoredb/fusion/result.py +399 -0
  26. singlestoredb/http/__init__.py +27 -0
  27. singlestoredb/{http.py → http/connection.py} +555 -154
  28. singlestoredb/management/__init__.py +3 -0
  29. singlestoredb/management/billing_usage.py +148 -0
  30. singlestoredb/management/cluster.py +14 -6
  31. singlestoredb/management/manager.py +100 -38
  32. singlestoredb/management/organization.py +188 -0
  33. singlestoredb/management/region.py +5 -5
  34. singlestoredb/management/utils.py +281 -2
  35. singlestoredb/management/workspace.py +1344 -49
  36. singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
  37. singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
  38. singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
  39. singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
  40. singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
  41. singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
  42. singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
  43. singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
  44. singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
  45. singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
  46. singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
  47. singlestoredb/mysql/converters.py +271 -0
  48. singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
  49. singlestoredb/mysql/err.py +92 -0
  50. singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
  51. singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
  52. singlestoredb/mysql/tests/__init__.py +19 -0
  53. singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
  54. singlestoredb/mysql/tests/conftest.py +37 -0
  55. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
  56. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
  57. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
  58. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
  59. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
  60. singlestoredb/mysql/tests/test_cursor.py +141 -0
  61. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
  62. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
  63. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
  64. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
  65. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
  66. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
  67. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  68. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
  69. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
  70. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
  71. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
  72. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
  73. singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
  74. singlestoredb/pytest.py +283 -0
  75. singlestoredb/tests/empty.sql +0 -0
  76. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  77. singlestoredb/tests/test.sql +210 -0
  78. singlestoredb/tests/test2.sql +1 -0
  79. singlestoredb/tests/test_basics.py +482 -115
  80. singlestoredb/tests/test_config.py +13 -13
  81. singlestoredb/tests/test_connection.py +241 -305
  82. singlestoredb/tests/test_dbapi.py +27 -0
  83. singlestoredb/tests/test_ext_func.py +1193 -0
  84. singlestoredb/tests/test_ext_func_data.py +1101 -0
  85. singlestoredb/tests/test_fusion.py +465 -0
  86. singlestoredb/tests/test_http.py +32 -26
  87. singlestoredb/tests/test_management.py +588 -8
  88. singlestoredb/tests/test_plugin.py +33 -0
  89. singlestoredb/tests/test_results.py +11 -12
  90. singlestoredb/tests/test_udf.py +687 -0
  91. singlestoredb/tests/utils.py +3 -2
  92. singlestoredb/utils/config.py +58 -0
  93. singlestoredb/utils/debug.py +13 -0
  94. singlestoredb/utils/mogrify.py +151 -0
  95. singlestoredb/utils/results.py +4 -1
  96. singlestoredb-1.0.4.dist-info/METADATA +139 -0
  97. singlestoredb-1.0.4.dist-info/RECORD +112 -0
  98. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
  99. singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
  100. singlestoredb/clients/pymysqlsv/converters.py +0 -365
  101. singlestoredb/clients/pymysqlsv/err.py +0 -144
  102. singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
  103. singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
  104. singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
  105. singlestoredb/drivers/__init__.py +0 -45
  106. singlestoredb/drivers/base.py +0 -198
  107. singlestoredb/drivers/cymysql.py +0 -38
  108. singlestoredb/drivers/http.py +0 -47
  109. singlestoredb/drivers/mariadb.py +0 -40
  110. singlestoredb/drivers/mysqlconnector.py +0 -49
  111. singlestoredb/drivers/mysqldb.py +0 -60
  112. singlestoredb/drivers/pymysql.py +0 -37
  113. singlestoredb/drivers/pymysqlsv.py +0 -35
  114. singlestoredb/drivers/pyodbc.py +0 -65
  115. singlestoredb-0.4.0.dist-info/METADATA +0 -111
  116. singlestoredb-0.4.0.dist-info/RECORD +0 -86
  117. /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
  118. /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
  119. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
  120. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env python
2
2
  # type: ignore
3
- """SingleStoreDB HTTP connection testing."""
3
+ """SingleStoreDB Management API testing."""
4
4
  import os
5
+ import pathlib
5
6
  import random
6
7
  import re
7
8
  import secrets
8
9
  import unittest
9
10
 
11
+ import pytest
12
+
10
13
  import singlestoredb as s2
11
14
 
12
15
 
16
+ TEST_DIR = pathlib.Path(os.path.dirname(__file__))
17
+
18
+
13
19
  def clean_name(s):
14
20
  """Change all non-word characters to -."""
15
21
  return re.sub(r'[^\w]', r'-', s).replace('_', '-').lower()
16
22
 
17
23
 
24
+ @pytest.mark.management
18
25
  class TestCluster(unittest.TestCase):
19
26
 
20
27
  manager = None
@@ -26,7 +33,7 @@ class TestCluster(unittest.TestCase):
26
33
  cls.manager = s2.manage_cluster()
27
34
 
28
35
  us_regions = [x for x in cls.manager.regions if 'US' in x.name]
29
- cls.password = secrets.token_urlsafe(20)
36
+ cls.password = secrets.token_urlsafe(20) + '-x&'
30
37
 
31
38
  cls.cluster = cls.manager.create_cluster(
32
39
  clean_name('cm-test-{}'.format(secrets.token_urlsafe(20)[:20])),
@@ -62,10 +69,24 @@ class TestCluster(unittest.TestCase):
62
69
  def test_regions(self):
63
70
  out = self.manager.regions
64
71
  providers = {x.provider for x in out}
72
+ names = [x.name for x in out]
65
73
  assert 'Azure' in providers, providers
66
74
  assert 'GCP' in providers, providers
67
75
  assert 'AWS' in providers, providers
68
76
 
77
+ objs = {}
78
+ ids = []
79
+ for item in out:
80
+ ids.append(item.id)
81
+ objs[item.id] = item
82
+ if item.name not in objs:
83
+ objs[item.name] = item
84
+
85
+ name = random.choice(names)
86
+ assert out[name] == objs[name]
87
+ id = random.choice(ids)
88
+ assert out[id] == objs[id]
89
+
69
90
  def test_clusters(self):
70
91
  clusters = self.manager.clusters
71
92
  ids = [x.id for x in clusters]
@@ -141,6 +162,8 @@ class TestCluster(unittest.TestCase):
141
162
  trues = ['1', 'on', 'true']
142
163
  pure_python = os.environ.get('SINGLESTOREDB_PURE_PYTHON', '0').lower() in trues
143
164
 
165
+ self.skipTest('Connection test is disable due to flakey server')
166
+
144
167
  if pure_python:
145
168
  self.skipTest('Connections through managed service are disabled')
146
169
 
@@ -163,6 +186,7 @@ class TestCluster(unittest.TestCase):
163
186
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
164
187
 
165
188
 
189
+ @pytest.mark.management
166
190
  class TestWorkspace(unittest.TestCase):
167
191
 
168
192
  manager = None
@@ -188,7 +212,7 @@ class TestWorkspace(unittest.TestCase):
188
212
 
189
213
  try:
190
214
  cls.workspace = cls.workspace_group.create_workspace(
191
- f'ws-test-{name}',
215
+ f'ws-test-{name}-x',
192
216
  wait_on_active=True,
193
217
  )
194
218
  except Exception:
@@ -222,19 +246,63 @@ class TestWorkspace(unittest.TestCase):
222
246
  def test_regions(self):
223
247
  out = self.manager.regions
224
248
  providers = {x.provider for x in out}
249
+ names = [x.name for x in out]
225
250
  assert 'Azure' in providers, providers
226
251
  assert 'GCP' in providers, providers
227
252
  assert 'AWS' in providers, providers
228
253
 
254
+ objs = {}
255
+ ids = []
256
+ for item in out:
257
+ ids.append(item.id)
258
+ objs[item.id] = item
259
+ if item.name not in objs:
260
+ objs[item.name] = item
261
+
262
+ name = random.choice(names)
263
+ assert out[name] == objs[name]
264
+ id = random.choice(ids)
265
+ assert out[id] == objs[id]
266
+
229
267
  def test_workspace_groups(self):
230
268
  workspace_groups = self.manager.workspace_groups
231
269
  ids = [x.id for x in workspace_groups]
232
- assert self.workspace_group.id in ids, ids
270
+ names = [x.name for x in workspace_groups]
271
+ assert self.workspace_group.id in ids
272
+ assert self.workspace_group.name in names
273
+
274
+ assert workspace_groups.ids() == ids
275
+ assert workspace_groups.names() == names
276
+
277
+ objs = {}
278
+ for item in workspace_groups:
279
+ objs[item.id] = item
280
+ objs[item.name] = item
281
+
282
+ name = random.choice(names)
283
+ assert workspace_groups[name] == objs[name]
284
+ id = random.choice(ids)
285
+ assert workspace_groups[id] == objs[id]
233
286
 
234
287
  def test_workspaces(self):
235
288
  spaces = self.workspace_group.workspaces
236
289
  ids = [x.id for x in spaces]
237
- assert self.workspace.id in ids, ids
290
+ names = [x.name for x in spaces]
291
+ assert self.workspace.id in ids
292
+ assert self.workspace.name in names
293
+
294
+ assert spaces.ids() == ids
295
+ assert spaces.names() == names
296
+
297
+ objs = {}
298
+ for item in spaces:
299
+ objs[item.id] = item
300
+ objs[item.name] = item
301
+
302
+ name = random.choice(names)
303
+ assert spaces[name] == objs[name]
304
+ id = random.choice(ids)
305
+ assert spaces[id] == objs[id]
238
306
 
239
307
  def test_get_workspace_group(self):
240
308
  group = self.manager.get_workspace_group(self.workspace_group.id)
@@ -293,6 +361,518 @@ class TestWorkspace(unittest.TestCase):
293
361
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
294
362
 
295
363
 
296
- if __name__ == '__main__':
297
- import nose2
298
- nose2.main()
364
+ @pytest.mark.management
365
+ class TestStage(unittest.TestCase):
366
+
367
+ manager = None
368
+ wg = None
369
+ password = None
370
+
371
+ @classmethod
372
+ def setUpClass(cls):
373
+ cls.manager = s2.manage_workspaces()
374
+
375
+ us_regions = [x for x in cls.manager.regions if 'US' in x.name]
376
+ cls.password = secrets.token_urlsafe(20)
377
+
378
+ name = clean_name(secrets.token_urlsafe(20)[:20])
379
+
380
+ cls.wg = cls.manager.create_workspace_group(
381
+ f'wg-test-{name}',
382
+ region=random.choice(us_regions).id,
383
+ admin_password=cls.password,
384
+ firewall_ranges=['0.0.0.0/0'],
385
+ )
386
+
387
+ @classmethod
388
+ def tearDownClass(cls):
389
+ if cls.wg is not None:
390
+ cls.wg.terminate(force=True)
391
+ cls.wg = None
392
+ cls.manager = None
393
+ cls.password = None
394
+
395
+ def test_upload_file(self):
396
+ st = self.wg.stage
397
+
398
+ root = st.info('/')
399
+ assert str(root.path) == '/'
400
+ assert root.type == 'directory'
401
+
402
+ # Upload file
403
+ f = st.upload_file(TEST_DIR / 'test.sql', 'upload_test.sql')
404
+ assert str(f.path) == 'upload_test.sql'
405
+ assert f.type == 'file'
406
+
407
+ # Download and compare to original
408
+ txt = f.download(encoding='utf-8')
409
+ assert txt == open(TEST_DIR / 'test.sql').read()
410
+
411
+ # Make sure we can't overwrite
412
+ with self.assertRaises(OSError):
413
+ st.upload_file(TEST_DIR / 'test.sql', 'upload_test.sql')
414
+
415
+ # Force overwrite with new content
416
+ f = st.upload_file(TEST_DIR / 'test2.sql', 'upload_test.sql', overwrite=True)
417
+ assert str(f.path) == 'upload_test.sql'
418
+ assert f.type == 'file'
419
+
420
+ # Verify new content
421
+ txt = f.download(encoding='utf-8')
422
+ assert txt == open(TEST_DIR / 'test2.sql').read()
423
+
424
+ # Try to upload folder
425
+ with self.assertRaises(IsADirectoryError):
426
+ st.upload_file(TEST_DIR, 'test3.sql')
427
+
428
+ lib = st.mkdir('/lib/')
429
+ assert str(lib.path) == 'lib/'
430
+ assert lib.type == 'directory'
431
+
432
+ # Try to overwrite stage folder with file
433
+ with self.assertRaises(IsADirectoryError):
434
+ st.upload_file(TEST_DIR / 'test2.sql', lib.path, overwrite=True)
435
+
436
+ # Write file into folder
437
+ f = st.upload_file(
438
+ TEST_DIR / 'test2.sql',
439
+ os.path.join(lib.path, 'upload_test2.sql'),
440
+ )
441
+ assert str(f.path) == 'lib/upload_test2.sql'
442
+ assert f.type == 'file'
443
+
444
+ def test_open(self):
445
+ st = self.wg.stage
446
+
447
+ # See if error is raised for non-existent file
448
+ with self.assertRaises(s2.ManagementError):
449
+ st.open('open_test.sql', 'r')
450
+
451
+ # Load test file
452
+ st.upload_file(TEST_DIR / 'test.sql', 'open_test.sql')
453
+
454
+ # Read file using `open`
455
+ with st.open('open_test.sql', 'r') as rfile:
456
+ assert rfile.read() == open(TEST_DIR / 'test.sql').read()
457
+
458
+ # Read file using `open` with 'rt' mode
459
+ with st.open('open_test.sql', 'rt') as rfile:
460
+ assert rfile.read() == open(TEST_DIR / 'test.sql').read()
461
+
462
+ # Read file using `open` with 'rb' mode
463
+ with st.open('open_test.sql', 'rb') as rfile:
464
+ assert rfile.read() == open(TEST_DIR / 'test.sql', 'rb').read()
465
+
466
+ # Read file using `open` with 'rb' mode
467
+ with self.assertRaises(ValueError):
468
+ with st.open('open_test.sql', 'b') as rfile:
469
+ pass
470
+
471
+ # Attempt overwrite file using `open` with mode 'x'
472
+ with self.assertRaises(OSError):
473
+ with st.open('open_test.sql', 'x') as wfile:
474
+ pass
475
+
476
+ # Attempt overwrite file using `open` with mode 'w'
477
+ with st.open('open_test.sql', 'w') as wfile:
478
+ wfile.write(open(TEST_DIR / 'test2.sql').read())
479
+
480
+ txt = st.download_file('open_test.sql', encoding='utf-8')
481
+
482
+ assert txt == open(TEST_DIR / 'test2.sql').read()
483
+
484
+ # Test writer without context manager
485
+ wfile = st.open('open_raw_test.sql', 'w')
486
+ for line in open(TEST_DIR / 'test.sql'):
487
+ wfile.write(line)
488
+ wfile.close()
489
+
490
+ txt = st.download_file('open_raw_test.sql', encoding='utf-8')
491
+
492
+ assert txt == open(TEST_DIR / 'test.sql').read()
493
+
494
+ # Test reader without context manager
495
+ rfile = st.open('open_raw_test.sql', 'r')
496
+ txt = ''
497
+ for line in rfile:
498
+ txt += line
499
+ rfile.close()
500
+
501
+ assert txt == open(TEST_DIR / 'test.sql').read()
502
+
503
+ def test_obj_open(self):
504
+ st = self.wg.stage
505
+
506
+ # Load test file
507
+ f = st.upload_file(TEST_DIR / 'test.sql', 'obj_open_test.sql')
508
+
509
+ # Read file using `open`
510
+ with f.open() as rfile:
511
+ assert rfile.read() == open(TEST_DIR / 'test.sql').read()
512
+
513
+ # Make sure directories error out
514
+ d = st.mkdir('obj_open_dir')
515
+ with self.assertRaises(IsADirectoryError):
516
+ d.open()
517
+
518
+ # Write file using `open`
519
+ with f.open('w', encoding='utf-8') as wfile:
520
+ wfile.write(open(TEST_DIR / 'test2.sql').read())
521
+
522
+ assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.sql').read()
523
+
524
+ # Test writer without context manager
525
+ wfile = f.open('w')
526
+ for line in open(TEST_DIR / 'test.sql'):
527
+ wfile.write(line)
528
+ wfile.close()
529
+
530
+ txt = st.download_file(f.path, encoding='utf-8')
531
+
532
+ assert txt == open(TEST_DIR / 'test.sql').read()
533
+
534
+ # Test reader without context manager
535
+ rfile = f.open('r')
536
+ txt = ''
537
+ for line in rfile:
538
+ txt += line
539
+ rfile.close()
540
+
541
+ assert txt == open(TEST_DIR / 'test.sql').read()
542
+
543
+ def test_os_directories(self):
544
+ st = self.wg.stage
545
+
546
+ # mkdir
547
+ st.mkdir('mkdir_test_1')
548
+ st.mkdir('mkdir_test_2')
549
+ with self.assertRaises(s2.ManagementError):
550
+ st.mkdir('mkdir_test_2/nest_1/nest_2')
551
+ st.mkdir('mkdir_test_2/nest_1')
552
+ st.mkdir('mkdir_test_2/nest_1/nest_2')
553
+ st.mkdir('mkdir_test_3')
554
+
555
+ assert st.exists('mkdir_test_1/')
556
+ assert st.exists('mkdir_test_2/')
557
+ assert st.exists('mkdir_test_2/nest_1/')
558
+ assert st.exists('mkdir_test_2/nest_1/nest_2/')
559
+ assert not st.exists('foo/')
560
+ assert not st.exists('foo/bar/')
561
+
562
+ assert st.is_dir('mkdir_test_1/')
563
+ assert st.is_dir('mkdir_test_2/')
564
+ assert st.is_dir('mkdir_test_2/nest_1/')
565
+ assert st.is_dir('mkdir_test_2/nest_1/nest_2/')
566
+
567
+ assert not st.is_file('mkdir_test_1/')
568
+ assert not st.is_file('mkdir_test_2/')
569
+ assert not st.is_file('mkdir_test_2/nest_1/')
570
+ assert not st.is_file('mkdir_test_2/nest_1/nest_2/')
571
+
572
+ out = st.listdir('/')
573
+ assert 'mkdir_test_1/' in out
574
+ assert 'mkdir_test_2/' in out
575
+ assert 'mkdir_test_2/nest_1/nest_2/' not in out
576
+
577
+ out = st.listdir('/', recursive=True)
578
+ assert 'mkdir_test_1/' in out
579
+ assert 'mkdir_test_2/' in out
580
+ assert 'mkdir_test_2/nest_1/nest_2/' in out
581
+
582
+ out = st.listdir('mkdir_test_2')
583
+ assert 'mkdir_test_1/' not in out
584
+ assert 'nest_1/' in out
585
+ assert 'nest_2/' not in out
586
+ assert 'nest_1/nest_2/' not in out
587
+
588
+ out = st.listdir('mkdir_test_2', recursive=True)
589
+ assert 'mkdir_test_1/' not in out
590
+ assert 'nest_1/' in out
591
+ assert 'nest_2/' not in out
592
+ assert 'nest_1/nest_2/' in out
593
+
594
+ # rmdir
595
+ before = st.listdir('/', recursive=True)
596
+ st.rmdir('mkdir_test_1/')
597
+ after = st.listdir('/', recursive=True)
598
+ assert 'mkdir_test_1/' in before
599
+ assert 'mkdir_test_1/' not in after
600
+ assert list(sorted(before)) == list(sorted(after + ['mkdir_test_1/']))
601
+
602
+ with self.assertRaises(OSError):
603
+ st.rmdir('mkdir_test_2/')
604
+
605
+ st.upload_file(TEST_DIR / 'test.sql', 'mkdir_test.sql')
606
+
607
+ with self.assertRaises(NotADirectoryError):
608
+ st.rmdir('mkdir_test.sql')
609
+
610
+ # removedirs
611
+ before = st.listdir('/')
612
+ st.removedirs('mkdir_test_2/')
613
+ after = st.listdir('/')
614
+ assert 'mkdir_test_2/' in before
615
+ assert 'mkdir_test_2/' not in after
616
+ assert list(sorted(before)) == list(sorted(after + ['mkdir_test_2/']))
617
+
618
+ with self.assertRaises(s2.ManagementError):
619
+ st.removedirs('mkdir_test.sql')
620
+
621
+ def test_os_files(self):
622
+ st = self.wg.stage
623
+
624
+ st.mkdir('files_test_1')
625
+ st.mkdir('files_test_1/nest_1')
626
+
627
+ st.upload_file(TEST_DIR / 'test.sql', 'files_test.sql')
628
+ st.upload_file(TEST_DIR / 'test.sql', 'files_test_1/nest_1/nested_files_test.sql')
629
+ st.upload_file(
630
+ TEST_DIR / 'test.sql',
631
+ 'files_test_1/nest_1/nested_files_test_2.sql',
632
+ )
633
+
634
+ # remove
635
+ with self.assertRaises(IsADirectoryError):
636
+ st.remove('files_test_1/')
637
+
638
+ before = st.listdir('/')
639
+ st.remove('files_test.sql')
640
+ after = st.listdir('/')
641
+ assert 'files_test.sql' in before
642
+ assert 'files_test.sql' not in after
643
+ assert list(sorted(before)) == list(sorted(after + ['files_test.sql']))
644
+
645
+ before = st.listdir('files_test_1/nest_1/')
646
+ st.remove('files_test_1/nest_1/nested_files_test.sql')
647
+ after = st.listdir('files_test_1/nest_1/')
648
+ assert 'nested_files_test.sql' in before
649
+ assert 'nested_files_test.sql' not in after
650
+ assert st.is_dir('files_test_1/nest_1/')
651
+
652
+ # Removing the last file does not remove empty directories
653
+ st.remove('files_test_1/nest_1/nested_files_test_2.sql')
654
+ assert not st.is_file('files_test_1/nest_1/nested_files_test_2.sql')
655
+ assert st.is_dir('files_test_1/nest_1/')
656
+ assert st.is_dir('files_test_1/')
657
+
658
+ st.removedirs('files_test_1')
659
+ assert not st.is_dir('files_test_1/nest_1/')
660
+ assert not st.is_dir('files_test_1/')
661
+
662
+ def test_os_rename(self):
663
+ st = self.wg.stage
664
+
665
+ st.upload_file(TEST_DIR / 'test.sql', 'rename_test.sql')
666
+
667
+ with self.assertRaises(s2.ManagementError):
668
+ st.upload_file(
669
+ TEST_DIR / 'test.sql',
670
+ 'rename_test_1/nest_1/nested_rename_test.sql',
671
+ )
672
+
673
+ st.mkdir('rename_test_1')
674
+ st.mkdir('rename_test_1/nest_1')
675
+
676
+ assert st.exists('/rename_test_1/nest_1/')
677
+
678
+ st.upload_file(
679
+ TEST_DIR / 'test.sql',
680
+ 'rename_test_1/nest_1/nested_rename_test.sql',
681
+ )
682
+
683
+ st.upload_file(
684
+ TEST_DIR / 'test.sql',
685
+ 'rename_test_1/nest_1/nested_rename_test_2.sql',
686
+ )
687
+
688
+ # rename file
689
+ assert 'rename_test.sql' in st.listdir('/')
690
+ assert 'rename_test_2.sql' not in st.listdir('/')
691
+ st.rename('rename_test.sql', 'rename_test_2.sql')
692
+ assert 'rename_test.sql' not in st.listdir('/')
693
+ assert 'rename_test_2.sql' in st.listdir('/')
694
+
695
+ # rename directory
696
+ assert 'rename_test_1/' in st.listdir('/')
697
+ assert 'rename_test_2/' not in st.listdir('/')
698
+ st.rename('rename_test_1/', 'rename_test_2/')
699
+ assert 'rename_test_1/' not in st.listdir('/')
700
+ assert 'rename_test_2/' in st.listdir('/')
701
+ assert st.is_file('rename_test_2/nest_1/nested_rename_test.sql')
702
+ assert st.is_file('rename_test_2/nest_1/nested_rename_test_2.sql')
703
+
704
+ # rename nested
705
+ assert 'rename_test_2/nest_1/nested_rename_test.sql' in st.listdir(
706
+ '/', recursive=True,
707
+ )
708
+ assert 'rename_test_2/nest_1/nested_rename_test_3.sql' not in st.listdir(
709
+ '/', recursive=True,
710
+ )
711
+ st.rename(
712
+ 'rename_test_2/nest_1/nested_rename_test.sql',
713
+ 'rename_test_2/nest_1/nested_rename_test_3.sql',
714
+ )
715
+ assert 'rename_test_2/nest_1/nested_rename_test.sql' not in st.listdir(
716
+ '/', recursive=True,
717
+ )
718
+ assert 'rename_test_2/nest_1/nested_rename_test_3.sql' in st.listdir(
719
+ '/', recursive=True,
720
+ )
721
+ assert not st.is_file('rename_test_2/nest_1/nested_rename_test.sql')
722
+ assert st.is_file('rename_test_2/nest_1/nested_rename_test_2.sql')
723
+ assert st.is_file('rename_test_2/nest_1/nested_rename_test_3.sql')
724
+
725
+ # non-existent file
726
+ with self.assertRaises(OSError):
727
+ st.rename('rename_foo.sql', 'rename_foo_2.sql')
728
+
729
+ # overwrite
730
+ with self.assertRaises(OSError):
731
+ st.rename(
732
+ 'rename_test_2.sql',
733
+ 'rename_test_2/nest_1/nested_rename_test_3.sql',
734
+ )
735
+
736
+ st.rename(
737
+ 'rename_test_2.sql',
738
+ 'rename_test_2/nest_1/nested_rename_test_3.sql', overwrite=True,
739
+ )
740
+
741
+ def test_stage_object(self):
742
+ st = self.wg.stage
743
+
744
+ st.mkdir('obj_test')
745
+ st.mkdir('obj_test/nest_1')
746
+
747
+ f1 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test.sql')
748
+ f2 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test/nest_1/obj_test.sql')
749
+ d2 = st.info('obj_test/nest_1/')
750
+
751
+ # is_file / is_dir
752
+ assert not f1.is_dir()
753
+ assert f1.is_file()
754
+ assert not f2.is_dir()
755
+ assert f2.is_file()
756
+ assert d2.is_dir()
757
+ assert not d2.is_file()
758
+
759
+ # abspath / basename / dirname / exists
760
+ assert f1.abspath() == 'obj_test.sql'
761
+ assert f1.basename() == 'obj_test.sql'
762
+ assert f1.dirname() == '/'
763
+ assert f1.exists()
764
+ assert f2.abspath() == 'obj_test/nest_1/obj_test.sql'
765
+ assert f2.basename() == 'obj_test.sql'
766
+ assert f2.dirname() == 'obj_test/nest_1/'
767
+ assert f2.exists()
768
+ assert d2.abspath() == 'obj_test/nest_1/'
769
+ assert d2.basename() == 'nest_1'
770
+ assert d2.dirname() == 'obj_test/'
771
+ assert d2.exists()
772
+
773
+ # download
774
+ assert f1.download(encoding='utf-8') == open(TEST_DIR / 'test.sql', 'r').read()
775
+ assert f1.download() == open(TEST_DIR / 'test.sql', 'rb').read()
776
+
777
+ # remove
778
+ with self.assertRaises(IsADirectoryError):
779
+ d2.remove()
780
+
781
+ assert st.is_file('obj_test.sql')
782
+ f1.remove()
783
+ assert not st.is_file('obj_test.sql')
784
+
785
+ # removedirs
786
+ with self.assertRaises(NotADirectoryError):
787
+ f2.removedirs()
788
+
789
+ assert st.exists(d2.path)
790
+ d2.removedirs()
791
+ assert not st.exists(d2.path)
792
+
793
+ # rmdir
794
+ f1 = st.upload_file(TEST_DIR / 'test.sql', 'obj_test.sql')
795
+ d2 = st.mkdir('obj_test/nest_1')
796
+
797
+ assert st.exists(f1.path)
798
+ assert st.exists(d2.path)
799
+
800
+ with self.assertRaises(NotADirectoryError):
801
+ f1.rmdir()
802
+
803
+ assert st.exists(f1.path)
804
+ assert st.exists(d2.path)
805
+
806
+ d2.rmdir()
807
+
808
+ assert not st.exists('obj_test/nest_1/')
809
+ assert not st.exists('obj_test')
810
+
811
+ # mtime / ctime
812
+ assert f1.getmtime() > 0
813
+ assert f1.getctime() > 0
814
+
815
+ # rename
816
+ assert st.exists('obj_test.sql')
817
+ assert not st.exists('obj_test_2.sql')
818
+ f1.rename('obj_test_2.sql')
819
+ assert not st.exists('obj_test.sql')
820
+ assert st.exists('obj_test_2.sql')
821
+ assert f1.abspath() == 'obj_test_2.sql'
822
+
823
+
824
+ @pytest.mark.management
825
+ class TestSecrets(unittest.TestCase):
826
+
827
+ manager = None
828
+ wg = None
829
+ password = None
830
+
831
+ @classmethod
832
+ def setUpClass(cls):
833
+ cls.manager = s2.manage_workspaces()
834
+
835
+ us_regions = [x for x in cls.manager.regions if 'US' in x.name]
836
+ cls.password = secrets.token_urlsafe(20)
837
+
838
+ name = clean_name(secrets.token_urlsafe(20)[:20])
839
+
840
+ cls.wg = cls.manager.create_workspace_group(
841
+ f'wg-test-{name}',
842
+ region=random.choice(us_regions).id,
843
+ admin_password=cls.password,
844
+ firewall_ranges=['0.0.0.0/0'],
845
+ )
846
+
847
+ @classmethod
848
+ def tearDownClass(cls):
849
+ if cls.wg is not None:
850
+ cls.wg.terminate(force=True)
851
+ cls.wg = None
852
+ cls.manager = None
853
+ cls.password = None
854
+
855
+ def test_get_secret(self):
856
+ # manually create secret and then get secret
857
+ # try to delete the secret if it exists
858
+ try:
859
+ secret = self.manager.organizations.current.get_secret('secret_name')
860
+
861
+ secret_id = secret.id
862
+
863
+ self.manager._delete(f'secrets/{secret_id}')
864
+ except s2.ManagementError:
865
+ pass
866
+
867
+ self.manager._post(
868
+ 'secrets',
869
+ json=dict(
870
+ name='secret_name',
871
+ value='secret_value',
872
+ ),
873
+ )
874
+
875
+ secret = self.manager.organizations.current.get_secret('secret_name')
876
+
877
+ assert secret.name == 'secret_name'
878
+ assert secret.value == 'secret_value'
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env python
2
+ # type: ignore
3
+ """SingleStoreDB Pytest Plugin testing
4
+
5
+ Each of these tests performs the same simple operation which
6
+ would fail if any other test had been run on the same database.
7
+ """
8
+ from singlestoredb.connection import Cursor
9
+
10
+ # pytest_plugins = ('singlestoredb.pytest',)
11
+
12
+ CREATE_TABLE_STATEMENT = 'create table test_dict (a text)'
13
+
14
+
15
+ def test_tempdb1(singlestoredb_tempdb: Cursor):
16
+ # alias the fixture
17
+ cursor = singlestoredb_tempdb
18
+
19
+ cursor.execute(CREATE_TABLE_STATEMENT)
20
+
21
+
22
+ def test_tempdb2(singlestoredb_tempdb: Cursor):
23
+ # alias the fixture
24
+ cursor = singlestoredb_tempdb
25
+
26
+ cursor.execute(CREATE_TABLE_STATEMENT)
27
+
28
+
29
+ def test_tempdb3(singlestoredb_tempdb: Cursor):
30
+ # alias the fixture
31
+ cursor = singlestoredb_tempdb
32
+
33
+ cursor.execute(CREATE_TABLE_STATEMENT)