singlestoredb 0.3.3__py3-none-any.whl → 1.0.3__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (121) hide show
  1. singlestoredb/__init__.py +33 -2
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +6 -4
  4. singlestoredb/config.py +116 -16
  5. singlestoredb/connection.py +489 -523
  6. singlestoredb/converters.py +275 -26
  7. singlestoredb/exceptions.py +30 -4
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/__init__.py +0 -0
  22. singlestoredb/fusion/handlers/stage.py +257 -0
  23. singlestoredb/fusion/handlers/utils.py +162 -0
  24. singlestoredb/fusion/handlers/workspace.py +412 -0
  25. singlestoredb/fusion/registry.py +164 -0
  26. singlestoredb/fusion/result.py +399 -0
  27. singlestoredb/http/__init__.py +27 -0
  28. singlestoredb/http/connection.py +1192 -0
  29. singlestoredb/management/__init__.py +3 -2
  30. singlestoredb/management/billing_usage.py +148 -0
  31. singlestoredb/management/cluster.py +19 -14
  32. singlestoredb/management/manager.py +100 -40
  33. singlestoredb/management/organization.py +188 -0
  34. singlestoredb/management/region.py +6 -8
  35. singlestoredb/management/utils.py +253 -4
  36. singlestoredb/management/workspace.py +1153 -35
  37. singlestoredb/mysql/__init__.py +177 -0
  38. singlestoredb/mysql/_auth.py +298 -0
  39. singlestoredb/mysql/charset.py +214 -0
  40. singlestoredb/mysql/connection.py +1814 -0
  41. singlestoredb/mysql/constants/CLIENT.py +38 -0
  42. singlestoredb/mysql/constants/COMMAND.py +32 -0
  43. singlestoredb/mysql/constants/CR.py +78 -0
  44. singlestoredb/mysql/constants/ER.py +474 -0
  45. singlestoredb/mysql/constants/FIELD_TYPE.py +32 -0
  46. singlestoredb/mysql/constants/FLAG.py +15 -0
  47. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  48. singlestoredb/mysql/constants/__init__.py +0 -0
  49. singlestoredb/mysql/converters.py +271 -0
  50. singlestoredb/mysql/cursors.py +713 -0
  51. singlestoredb/mysql/err.py +92 -0
  52. singlestoredb/mysql/optionfile.py +20 -0
  53. singlestoredb/mysql/protocol.py +388 -0
  54. singlestoredb/mysql/tests/__init__.py +19 -0
  55. singlestoredb/mysql/tests/base.py +126 -0
  56. singlestoredb/mysql/tests/conftest.py +37 -0
  57. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  58. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  59. singlestoredb/mysql/tests/test_basic.py +452 -0
  60. singlestoredb/mysql/tests/test_connection.py +851 -0
  61. singlestoredb/mysql/tests/test_converters.py +58 -0
  62. singlestoredb/mysql/tests/test_cursor.py +141 -0
  63. singlestoredb/mysql/tests/test_err.py +16 -0
  64. singlestoredb/mysql/tests/test_issues.py +514 -0
  65. singlestoredb/mysql/tests/test_load_local.py +75 -0
  66. singlestoredb/mysql/tests/test_nextset.py +88 -0
  67. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  68. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  69. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  70. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  71. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  72. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  73. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  74. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  75. singlestoredb/mysql/times.py +23 -0
  76. singlestoredb/pytest.py +283 -0
  77. singlestoredb/tests/empty.sql +0 -0
  78. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  79. singlestoredb/tests/test.sql +210 -0
  80. singlestoredb/tests/test2.sql +1 -0
  81. singlestoredb/tests/test_basics.py +482 -117
  82. singlestoredb/tests/test_config.py +13 -15
  83. singlestoredb/tests/test_connection.py +241 -289
  84. singlestoredb/tests/test_dbapi.py +27 -0
  85. singlestoredb/tests/test_exceptions.py +0 -2
  86. singlestoredb/tests/test_ext_func.py +1193 -0
  87. singlestoredb/tests/test_ext_func_data.py +1101 -0
  88. singlestoredb/tests/test_fusion.py +465 -0
  89. singlestoredb/tests/test_http.py +32 -28
  90. singlestoredb/tests/test_management.py +588 -10
  91. singlestoredb/tests/test_plugin.py +33 -0
  92. singlestoredb/tests/test_results.py +11 -14
  93. singlestoredb/tests/test_types.py +0 -2
  94. singlestoredb/tests/test_udf.py +687 -0
  95. singlestoredb/tests/test_xdict.py +0 -2
  96. singlestoredb/tests/utils.py +3 -4
  97. singlestoredb/types.py +4 -5
  98. singlestoredb/utils/config.py +71 -12
  99. singlestoredb/utils/convert_rows.py +0 -2
  100. singlestoredb/utils/debug.py +13 -0
  101. singlestoredb/utils/mogrify.py +151 -0
  102. singlestoredb/utils/results.py +4 -3
  103. singlestoredb/utils/xdict.py +12 -12
  104. singlestoredb-1.0.3.dist-info/METADATA +139 -0
  105. singlestoredb-1.0.3.dist-info/RECORD +112 -0
  106. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/WHEEL +1 -1
  107. singlestoredb-1.0.3.dist-info/entry_points.txt +2 -0
  108. singlestoredb/drivers/__init__.py +0 -46
  109. singlestoredb/drivers/base.py +0 -200
  110. singlestoredb/drivers/cymysql.py +0 -40
  111. singlestoredb/drivers/http.py +0 -49
  112. singlestoredb/drivers/mariadb.py +0 -42
  113. singlestoredb/drivers/mysqlconnector.py +0 -51
  114. singlestoredb/drivers/mysqldb.py +0 -62
  115. singlestoredb/drivers/pymysql.py +0 -39
  116. singlestoredb/drivers/pyodbc.py +0 -67
  117. singlestoredb/http.py +0 -794
  118. singlestoredb-0.3.3.dist-info/METADATA +0 -105
  119. singlestoredb-0.3.3.dist-info/RECORD +0 -46
  120. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/LICENSE +0 -0
  121. {singlestoredb-0.3.3.dist-info → singlestoredb-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,27 @@
1
1
  #!/usr/bin/env python
2
2
  # type: ignore
3
- """SingleStoreDB HTTP connection testing."""
4
- from __future__ import annotations
5
-
3
+ """SingleStoreDB Management API testing."""
6
4
  import os
5
+ import pathlib
7
6
  import random
8
7
  import re
9
8
  import secrets
10
9
  import unittest
11
10
 
11
+ import pytest
12
+
12
13
  import singlestoredb as s2
13
14
 
14
15
 
16
+ TEST_DIR = pathlib.Path(os.path.dirname(__file__))
17
+
18
+
15
19
  def clean_name(s):
16
20
  """Change all non-word characters to -."""
17
21
  return re.sub(r'[^\w]', r'-', s).replace('_', '-').lower()
18
22
 
19
23
 
24
+ @pytest.mark.management
20
25
  class TestCluster(unittest.TestCase):
21
26
 
22
27
  manager = None
@@ -28,7 +33,7 @@ class TestCluster(unittest.TestCase):
28
33
  cls.manager = s2.manage_cluster()
29
34
 
30
35
  us_regions = [x for x in cls.manager.regions if 'US' in x.name]
31
- cls.password = secrets.token_urlsafe(20)
36
+ cls.password = secrets.token_urlsafe(20) + '-x&'
32
37
 
33
38
  cls.cluster = cls.manager.create_cluster(
34
39
  clean_name('cm-test-{}'.format(secrets.token_urlsafe(20)[:20])),
@@ -64,10 +69,24 @@ class TestCluster(unittest.TestCase):
64
69
  def test_regions(self):
65
70
  out = self.manager.regions
66
71
  providers = {x.provider for x in out}
72
+ names = [x.name for x in out]
67
73
  assert 'Azure' in providers, providers
68
74
  assert 'GCP' in providers, providers
69
75
  assert 'AWS' in providers, providers
70
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
+
71
90
  def test_clusters(self):
72
91
  clusters = self.manager.clusters
73
92
  ids = [x.id for x in clusters]
@@ -143,6 +162,8 @@ class TestCluster(unittest.TestCase):
143
162
  trues = ['1', 'on', 'true']
144
163
  pure_python = os.environ.get('SINGLESTOREDB_PURE_PYTHON', '0').lower() in trues
145
164
 
165
+ self.skipTest('Connection test is disable due to flakey server')
166
+
146
167
  if pure_python:
147
168
  self.skipTest('Connections through managed service are disabled')
148
169
 
@@ -165,6 +186,7 @@ class TestCluster(unittest.TestCase):
165
186
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
166
187
 
167
188
 
189
+ @pytest.mark.management
168
190
  class TestWorkspace(unittest.TestCase):
169
191
 
170
192
  manager = None
@@ -190,7 +212,7 @@ class TestWorkspace(unittest.TestCase):
190
212
 
191
213
  try:
192
214
  cls.workspace = cls.workspace_group.create_workspace(
193
- f'ws-test-{name}',
215
+ f'ws-test-{name}-x',
194
216
  wait_on_active=True,
195
217
  )
196
218
  except Exception:
@@ -224,19 +246,63 @@ class TestWorkspace(unittest.TestCase):
224
246
  def test_regions(self):
225
247
  out = self.manager.regions
226
248
  providers = {x.provider for x in out}
249
+ names = [x.name for x in out]
227
250
  assert 'Azure' in providers, providers
228
251
  assert 'GCP' in providers, providers
229
252
  assert 'AWS' in providers, providers
230
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
+
231
267
  def test_workspace_groups(self):
232
268
  workspace_groups = self.manager.workspace_groups
233
269
  ids = [x.id for x in workspace_groups]
234
- 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]
235
286
 
236
287
  def test_workspaces(self):
237
288
  spaces = self.workspace_group.workspaces
238
289
  ids = [x.id for x in spaces]
239
- 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]
240
306
 
241
307
  def test_get_workspace_group(self):
242
308
  group = self.manager.get_workspace_group(self.workspace_group.id)
@@ -295,6 +361,518 @@ class TestWorkspace(unittest.TestCase):
295
361
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
296
362
 
297
363
 
298
- if __name__ == '__main__':
299
- import nose2
300
- 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)