singlestoredb 1.16.1__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.
- singlestoredb/__init__.py +75 -0
- singlestoredb/ai/__init__.py +2 -0
- singlestoredb/ai/chat.py +139 -0
- singlestoredb/ai/embeddings.py +128 -0
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/apps/__init__.py +3 -0
- singlestoredb/apps/_cloud_functions.py +90 -0
- singlestoredb/apps/_config.py +72 -0
- singlestoredb/apps/_connection_info.py +18 -0
- singlestoredb/apps/_dashboards.py +47 -0
- singlestoredb/apps/_process.py +32 -0
- singlestoredb/apps/_python_udfs.py +100 -0
- singlestoredb/apps/_stdout_supress.py +30 -0
- singlestoredb/apps/_uvicorn_util.py +36 -0
- singlestoredb/auth.py +245 -0
- singlestoredb/config.py +484 -0
- singlestoredb/connection.py +1487 -0
- singlestoredb/converters.py +950 -0
- singlestoredb/docstring/__init__.py +33 -0
- singlestoredb/docstring/attrdoc.py +126 -0
- singlestoredb/docstring/common.py +230 -0
- singlestoredb/docstring/epydoc.py +267 -0
- singlestoredb/docstring/google.py +412 -0
- singlestoredb/docstring/numpydoc.py +562 -0
- singlestoredb/docstring/parser.py +100 -0
- singlestoredb/docstring/py.typed +1 -0
- singlestoredb/docstring/rest.py +256 -0
- singlestoredb/docstring/tests/__init__.py +1 -0
- singlestoredb/docstring/tests/_pydoctor.py +21 -0
- singlestoredb/docstring/tests/test_epydoc.py +729 -0
- singlestoredb/docstring/tests/test_google.py +1007 -0
- singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
- singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
- singlestoredb/docstring/tests/test_parser.py +248 -0
- singlestoredb/docstring/tests/test_rest.py +547 -0
- singlestoredb/docstring/tests/test_util.py +70 -0
- singlestoredb/docstring/util.py +141 -0
- singlestoredb/exceptions.py +120 -0
- singlestoredb/functions/__init__.py +16 -0
- singlestoredb/functions/decorator.py +201 -0
- singlestoredb/functions/dtypes.py +1793 -0
- singlestoredb/functions/ext/__init__.py +1 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +2133 -0
- singlestoredb/functions/ext/json.py +420 -0
- singlestoredb/functions/ext/mmap.py +413 -0
- singlestoredb/functions/ext/rowdat_1.py +724 -0
- singlestoredb/functions/ext/timer.py +89 -0
- singlestoredb/functions/ext/utils.py +218 -0
- singlestoredb/functions/signature.py +1578 -0
- singlestoredb/functions/typing/__init__.py +41 -0
- singlestoredb/functions/typing/numpy.py +20 -0
- singlestoredb/functions/typing/pandas.py +2 -0
- singlestoredb/functions/typing/polars.py +2 -0
- singlestoredb/functions/typing/pyarrow.py +2 -0
- singlestoredb/functions/utils.py +421 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +916 -0
- singlestoredb/fusion/handlers/__init__.py +0 -0
- singlestoredb/fusion/handlers/export.py +525 -0
- singlestoredb/fusion/handlers/files.py +690 -0
- singlestoredb/fusion/handlers/job.py +660 -0
- singlestoredb/fusion/handlers/models.py +250 -0
- singlestoredb/fusion/handlers/stage.py +502 -0
- singlestoredb/fusion/handlers/utils.py +324 -0
- singlestoredb/fusion/handlers/workspace.py +956 -0
- singlestoredb/fusion/registry.py +249 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/http/connection.py +1267 -0
- singlestoredb/magics/__init__.py +34 -0
- singlestoredb/magics/run_personal.py +137 -0
- singlestoredb/magics/run_shared.py +134 -0
- singlestoredb/management/__init__.py +9 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +462 -0
- singlestoredb/management/export.py +295 -0
- singlestoredb/management/files.py +1102 -0
- singlestoredb/management/inference_api.py +105 -0
- singlestoredb/management/job.py +887 -0
- singlestoredb/management/manager.py +373 -0
- singlestoredb/management/organization.py +226 -0
- singlestoredb/management/region.py +169 -0
- singlestoredb/management/utils.py +423 -0
- singlestoredb/management/workspace.py +1927 -0
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +2032 -0
- singlestoredb/mysql/constants/CLIENT.py +38 -0
- singlestoredb/mysql/constants/COMMAND.py +32 -0
- singlestoredb/mysql/constants/CR.py +78 -0
- singlestoredb/mysql/constants/ER.py +474 -0
- singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +896 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +450 -0
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/mysql/tests/base.py +126 -0
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/mysql/tests/test_DictCursor.py +132 -0
- singlestoredb/mysql/tests/test_SSCursor.py +141 -0
- singlestoredb/mysql/tests/test_basic.py +452 -0
- singlestoredb/mysql/tests/test_connection.py +851 -0
- singlestoredb/mysql/tests/test_converters.py +58 -0
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/mysql/tests/test_err.py +16 -0
- singlestoredb/mysql/tests/test_issues.py +514 -0
- singlestoredb/mysql/tests/test_load_local.py +75 -0
- singlestoredb/mysql/tests/test_nextset.py +88 -0
- singlestoredb/mysql/tests/test_optionfile.py +27 -0
- singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
- singlestoredb/mysql/times.py +23 -0
- singlestoredb/notebook/__init__.py +16 -0
- singlestoredb/notebook/_objects.py +213 -0
- singlestoredb/notebook/_portal.py +352 -0
- singlestoredb/py.typed +0 -0
- singlestoredb/pytest.py +352 -0
- singlestoredb/server/__init__.py +0 -0
- singlestoredb/server/docker.py +452 -0
- singlestoredb/server/free_tier.py +267 -0
- singlestoredb/tests/__init__.py +0 -0
- singlestoredb/tests/alltypes.sql +307 -0
- singlestoredb/tests/alltypes_no_nulls.sql +208 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +702 -0
- singlestoredb/tests/local_infile.csv +3 -0
- singlestoredb/tests/test.ipynb +18 -0
- singlestoredb/tests/test.sql +680 -0
- singlestoredb/tests/test2.ipynb +18 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +1332 -0
- singlestoredb/tests/test_config.py +318 -0
- singlestoredb/tests/test_connection.py +3103 -0
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +45 -0
- singlestoredb/tests/test_ext_func.py +1472 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +1527 -0
- singlestoredb/tests/test_http.py +288 -0
- singlestoredb/tests/test_management.py +1599 -0
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +171 -0
- singlestoredb/tests/test_types.py +132 -0
- singlestoredb/tests/test_udf.py +737 -0
- singlestoredb/tests/test_udf_returns.py +459 -0
- singlestoredb/tests/test_vectorstore.py +51 -0
- singlestoredb/tests/test_xdict.py +333 -0
- singlestoredb/tests/utils.py +141 -0
- singlestoredb/types.py +373 -0
- singlestoredb/utils/__init__.py +0 -0
- singlestoredb/utils/config.py +950 -0
- singlestoredb/utils/convert_rows.py +69 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/dtypes.py +205 -0
- singlestoredb/utils/events.py +65 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +585 -0
- singlestoredb/utils/xdict.py +425 -0
- singlestoredb/vectorstore.py +192 -0
- singlestoredb/warnings.py +5 -0
- singlestoredb-1.16.1.dist-info/METADATA +165 -0
- singlestoredb-1.16.1.dist-info/RECORD +183 -0
- singlestoredb-1.16.1.dist-info/WHEEL +5 -0
- singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
- singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
- singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
- sqlx/__init__.py +4 -0
- sqlx/magic.py +113 -0
|
@@ -0,0 +1,1599 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# type: ignore
|
|
3
|
+
"""SingleStoreDB Management API testing."""
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import random
|
|
7
|
+
import re
|
|
8
|
+
import secrets
|
|
9
|
+
import unittest
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
import singlestoredb as s2
|
|
14
|
+
from singlestoredb.management.job import Status
|
|
15
|
+
from singlestoredb.management.job import TargetType
|
|
16
|
+
from singlestoredb.management.region import Region
|
|
17
|
+
from singlestoredb.management.utils import NamedList
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TEST_DIR = pathlib.Path(os.path.dirname(__file__))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def clean_name(s):
|
|
24
|
+
"""Change all non-word characters to -."""
|
|
25
|
+
return re.sub(r'[^\w]', r'-', s).replace('_', '-').lower()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def shared_database_name(s):
|
|
29
|
+
"""Return a shared database name. Cannot contain special characters except -"""
|
|
30
|
+
return re.sub(r'[^\w]', '', s).replace('-', '_').lower()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.skip(reason='Legacy cluster Management API is going away')
|
|
34
|
+
@pytest.mark.management
|
|
35
|
+
class TestCluster(unittest.TestCase):
|
|
36
|
+
|
|
37
|
+
manager = None
|
|
38
|
+
cluster = None
|
|
39
|
+
password = None
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def setUpClass(cls):
|
|
43
|
+
cls.manager = s2.manage_cluster()
|
|
44
|
+
|
|
45
|
+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
|
|
46
|
+
cls.password = secrets.token_urlsafe(20) + '-x&$'
|
|
47
|
+
|
|
48
|
+
cls.cluster = cls.manager.create_cluster(
|
|
49
|
+
clean_name('cm-test-{}'.format(secrets.token_urlsafe(20)[:20])),
|
|
50
|
+
region=random.choice(us_regions).id,
|
|
51
|
+
admin_password=cls.password,
|
|
52
|
+
firewall_ranges=['0.0.0.0/0'],
|
|
53
|
+
expires_at='1h',
|
|
54
|
+
size='S-00',
|
|
55
|
+
wait_on_active=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def tearDownClass(cls):
|
|
60
|
+
if cls.cluster is not None:
|
|
61
|
+
cls.cluster.terminate()
|
|
62
|
+
cls.cluster = None
|
|
63
|
+
cls.manager = None
|
|
64
|
+
cls.password = None
|
|
65
|
+
|
|
66
|
+
def test_str(self):
|
|
67
|
+
assert self.cluster.name in str(self.cluster.name)
|
|
68
|
+
|
|
69
|
+
def test_repr(self):
|
|
70
|
+
assert repr(self.cluster) == str(self.cluster)
|
|
71
|
+
|
|
72
|
+
def test_region_str(self):
|
|
73
|
+
s = str(self.cluster.region)
|
|
74
|
+
assert 'Azure' in s or 'GCP' in s or 'AWS' in s, s
|
|
75
|
+
|
|
76
|
+
def test_region_repr(self):
|
|
77
|
+
assert repr(self.cluster.region) == str(self.cluster.region)
|
|
78
|
+
|
|
79
|
+
def test_regions(self):
|
|
80
|
+
out = self.manager.regions
|
|
81
|
+
providers = {x.provider for x in out}
|
|
82
|
+
names = [x.name for x in out]
|
|
83
|
+
assert 'Azure' in providers, providers
|
|
84
|
+
assert 'GCP' in providers, providers
|
|
85
|
+
assert 'AWS' in providers, providers
|
|
86
|
+
|
|
87
|
+
objs = {}
|
|
88
|
+
ids = []
|
|
89
|
+
for item in out:
|
|
90
|
+
ids.append(item.id)
|
|
91
|
+
objs[item.id] = item
|
|
92
|
+
if item.name not in objs:
|
|
93
|
+
objs[item.name] = item
|
|
94
|
+
|
|
95
|
+
name = random.choice(names)
|
|
96
|
+
assert out[name] == objs[name]
|
|
97
|
+
id = random.choice(ids)
|
|
98
|
+
assert out[id] == objs[id]
|
|
99
|
+
|
|
100
|
+
def test_clusters(self):
|
|
101
|
+
clusters = self.manager.clusters
|
|
102
|
+
ids = [x.id for x in clusters]
|
|
103
|
+
assert self.cluster.id in ids, ids
|
|
104
|
+
|
|
105
|
+
def test_get_cluster(self):
|
|
106
|
+
clus = self.manager.get_cluster(self.cluster.id)
|
|
107
|
+
assert clus.id == self.cluster.id, clus.id
|
|
108
|
+
|
|
109
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
110
|
+
clus = self.manager.get_cluster('bad id')
|
|
111
|
+
|
|
112
|
+
assert 'UUID' in cm.exception.msg, cm.exception.msg
|
|
113
|
+
|
|
114
|
+
def test_update(self):
|
|
115
|
+
assert self.cluster.name.startswith('cm-test-')
|
|
116
|
+
|
|
117
|
+
name = self.cluster.name.replace('cm-test-', 'cm-foo-')
|
|
118
|
+
self.cluster.update(name=name)
|
|
119
|
+
|
|
120
|
+
clus = self.manager.get_cluster(self.cluster.id)
|
|
121
|
+
assert clus.name == name, clus.name
|
|
122
|
+
|
|
123
|
+
def test_suspend_resume(self):
|
|
124
|
+
trues = ['1', 'on', 'true']
|
|
125
|
+
do_test = os.environ.get('SINGLESTOREDB_TEST_SUSPEND', '0').lower() in trues
|
|
126
|
+
|
|
127
|
+
if not do_test:
|
|
128
|
+
self.skipTest(
|
|
129
|
+
'Suspend / resume tests skipped by default due to '
|
|
130
|
+
'being time consuming; set SINGLESTOREDB_TEST_SUSPEND=1 '
|
|
131
|
+
'to enable',
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert self.cluster.state != 'Suspended', self.cluster.state
|
|
135
|
+
|
|
136
|
+
self.cluster.suspend(wait_on_suspended=True)
|
|
137
|
+
assert self.cluster.state == 'Suspended', self.cluster.state
|
|
138
|
+
|
|
139
|
+
self.cluster.resume(wait_on_resumed=True)
|
|
140
|
+
assert self.cluster.state == 'Active', self.cluster.state
|
|
141
|
+
|
|
142
|
+
def test_no_manager(self):
|
|
143
|
+
clus = self.manager.get_cluster(self.cluster.id)
|
|
144
|
+
clus._manager = None
|
|
145
|
+
|
|
146
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
147
|
+
clus.refresh()
|
|
148
|
+
|
|
149
|
+
assert 'No cluster manager' in cm.exception.msg, cm.exception.msg
|
|
150
|
+
|
|
151
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
152
|
+
clus.update()
|
|
153
|
+
|
|
154
|
+
assert 'No cluster manager' in cm.exception.msg, cm.exception.msg
|
|
155
|
+
|
|
156
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
157
|
+
clus.suspend()
|
|
158
|
+
|
|
159
|
+
assert 'No cluster manager' in cm.exception.msg, cm.exception.msg
|
|
160
|
+
|
|
161
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
162
|
+
clus.resume()
|
|
163
|
+
|
|
164
|
+
assert 'No cluster manager' in cm.exception.msg, cm.exception.msg
|
|
165
|
+
|
|
166
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
167
|
+
clus.terminate()
|
|
168
|
+
|
|
169
|
+
assert 'No cluster manager' in cm.exception.msg, cm.exception.msg
|
|
170
|
+
|
|
171
|
+
def test_connect(self):
|
|
172
|
+
trues = ['1', 'on', 'true']
|
|
173
|
+
pure_python = os.environ.get('SINGLESTOREDB_PURE_PYTHON', '0').lower() in trues
|
|
174
|
+
|
|
175
|
+
self.skipTest('Connection test is disable due to flakey server')
|
|
176
|
+
|
|
177
|
+
if pure_python:
|
|
178
|
+
self.skipTest('Connections through managed service are disabled')
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
with self.cluster.connect(user='admin', password=self.password) as conn:
|
|
182
|
+
with conn.cursor() as cur:
|
|
183
|
+
cur.execute('show databases')
|
|
184
|
+
assert 'cluster' in [x[0] for x in list(cur)]
|
|
185
|
+
except s2.ManagementError as exc:
|
|
186
|
+
if 'endpoint has not been set' not in str(exc):
|
|
187
|
+
self.skipTest('No endpoint in response. Skipping connection test.')
|
|
188
|
+
|
|
189
|
+
# Test missing endpoint
|
|
190
|
+
clus = self.manager.get_cluster(self.cluster.id)
|
|
191
|
+
clus.endpoint = None
|
|
192
|
+
|
|
193
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
194
|
+
clus.connect(user='admin', password=self.password)
|
|
195
|
+
|
|
196
|
+
assert 'endpoint' in cm.exception.msg, cm.exception.msg
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.mark.management
|
|
200
|
+
class TestWorkspace(unittest.TestCase):
|
|
201
|
+
|
|
202
|
+
manager = None
|
|
203
|
+
workspace_group = None
|
|
204
|
+
workspace = None
|
|
205
|
+
password = None
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def setUpClass(cls):
|
|
209
|
+
cls.manager = s2.manage_workspaces()
|
|
210
|
+
|
|
211
|
+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
|
|
212
|
+
cls.password = secrets.token_urlsafe(20) + '-x&$'
|
|
213
|
+
|
|
214
|
+
name = clean_name(secrets.token_urlsafe(20)[:20])
|
|
215
|
+
|
|
216
|
+
cls.workspace_group = cls.manager.create_workspace_group(
|
|
217
|
+
f'wg-test-{name}',
|
|
218
|
+
region=random.choice(us_regions).id,
|
|
219
|
+
admin_password=cls.password,
|
|
220
|
+
firewall_ranges=['0.0.0.0/0'],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
cls.workspace = cls.workspace_group.create_workspace(
|
|
225
|
+
f'ws-test-{name}-x',
|
|
226
|
+
wait_on_active=True,
|
|
227
|
+
)
|
|
228
|
+
except Exception:
|
|
229
|
+
cls.workspace_group.terminate(force=True)
|
|
230
|
+
raise
|
|
231
|
+
|
|
232
|
+
@classmethod
|
|
233
|
+
def tearDownClass(cls):
|
|
234
|
+
if cls.workspace_group is not None:
|
|
235
|
+
cls.workspace_group.terminate(force=True)
|
|
236
|
+
cls.workspace_group = None
|
|
237
|
+
cls.workspace = None
|
|
238
|
+
cls.manager = None
|
|
239
|
+
cls.password = None
|
|
240
|
+
|
|
241
|
+
def test_str(self):
|
|
242
|
+
assert self.workspace.name in str(self.workspace.name)
|
|
243
|
+
assert self.workspace_group.name in str(self.workspace_group.name)
|
|
244
|
+
|
|
245
|
+
def test_repr(self):
|
|
246
|
+
assert repr(self.workspace) == str(self.workspace)
|
|
247
|
+
assert repr(self.workspace_group) == str(self.workspace_group)
|
|
248
|
+
|
|
249
|
+
def test_region_str(self):
|
|
250
|
+
s = str(self.workspace_group.region)
|
|
251
|
+
assert 'Azure' in s or 'GCP' in s or 'AWS' in s, s
|
|
252
|
+
|
|
253
|
+
def test_region_repr(self):
|
|
254
|
+
assert repr(self.workspace_group.region) == str(self.workspace_group.region)
|
|
255
|
+
|
|
256
|
+
def test_regions(self):
|
|
257
|
+
out = self.manager.regions
|
|
258
|
+
providers = {x.provider for x in out}
|
|
259
|
+
names = [x.name for x in out]
|
|
260
|
+
assert 'Azure' in providers, providers
|
|
261
|
+
assert 'GCP' in providers, providers
|
|
262
|
+
assert 'AWS' in providers, providers
|
|
263
|
+
|
|
264
|
+
objs = {}
|
|
265
|
+
ids = []
|
|
266
|
+
for item in out:
|
|
267
|
+
ids.append(item.id)
|
|
268
|
+
objs[item.id] = item
|
|
269
|
+
if item.name not in objs:
|
|
270
|
+
objs[item.name] = item
|
|
271
|
+
|
|
272
|
+
name = random.choice(names)
|
|
273
|
+
assert out[name] == objs[name]
|
|
274
|
+
id = random.choice(ids)
|
|
275
|
+
assert out[id] == objs[id]
|
|
276
|
+
|
|
277
|
+
def test_workspace_groups(self):
|
|
278
|
+
workspace_groups = self.manager.workspace_groups
|
|
279
|
+
ids = [x.id for x in workspace_groups]
|
|
280
|
+
names = [x.name for x in workspace_groups]
|
|
281
|
+
assert self.workspace_group.id in ids
|
|
282
|
+
assert self.workspace_group.name in names
|
|
283
|
+
|
|
284
|
+
assert workspace_groups.ids() == ids
|
|
285
|
+
assert workspace_groups.names() == names
|
|
286
|
+
|
|
287
|
+
objs = {}
|
|
288
|
+
for item in workspace_groups:
|
|
289
|
+
objs[item.id] = item
|
|
290
|
+
objs[item.name] = item
|
|
291
|
+
|
|
292
|
+
name = random.choice(names)
|
|
293
|
+
assert workspace_groups[name] == objs[name]
|
|
294
|
+
id = random.choice(ids)
|
|
295
|
+
assert workspace_groups[id] == objs[id]
|
|
296
|
+
|
|
297
|
+
def test_workspaces(self):
|
|
298
|
+
spaces = self.workspace_group.workspaces
|
|
299
|
+
ids = [x.id for x in spaces]
|
|
300
|
+
names = [x.name for x in spaces]
|
|
301
|
+
assert self.workspace.id in ids
|
|
302
|
+
assert self.workspace.name in names
|
|
303
|
+
|
|
304
|
+
assert spaces.ids() == ids
|
|
305
|
+
assert spaces.names() == names
|
|
306
|
+
|
|
307
|
+
objs = {}
|
|
308
|
+
for item in spaces:
|
|
309
|
+
objs[item.id] = item
|
|
310
|
+
objs[item.name] = item
|
|
311
|
+
|
|
312
|
+
name = random.choice(names)
|
|
313
|
+
assert spaces[name] == objs[name]
|
|
314
|
+
id = random.choice(ids)
|
|
315
|
+
assert spaces[id] == objs[id]
|
|
316
|
+
|
|
317
|
+
def test_get_workspace_group(self):
|
|
318
|
+
group = self.manager.get_workspace_group(self.workspace_group.id)
|
|
319
|
+
assert group.id == self.workspace_group.id, group.id
|
|
320
|
+
|
|
321
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
322
|
+
group = self.manager.get_workspace_group('bad id')
|
|
323
|
+
|
|
324
|
+
assert 'UUID' in cm.exception.msg, cm.exception.msg
|
|
325
|
+
|
|
326
|
+
def test_get_workspace(self):
|
|
327
|
+
space = self.manager.get_workspace(self.workspace.id)
|
|
328
|
+
assert space.id == self.workspace.id, space.id
|
|
329
|
+
|
|
330
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
331
|
+
space = self.manager.get_workspace('bad id')
|
|
332
|
+
|
|
333
|
+
assert 'UUID' in cm.exception.msg, cm.exception.msg
|
|
334
|
+
|
|
335
|
+
def test_update(self):
|
|
336
|
+
assert self.workspace_group.name.startswith('wg-test-')
|
|
337
|
+
|
|
338
|
+
name = self.workspace_group.name.replace('wg-test-', 'wg-foo-')
|
|
339
|
+
self.workspace_group.update(name=name)
|
|
340
|
+
|
|
341
|
+
group = self.manager.get_workspace_group(self.workspace_group.id)
|
|
342
|
+
assert group.name == name, group.name
|
|
343
|
+
|
|
344
|
+
def test_no_manager(self):
|
|
345
|
+
space = self.manager.get_workspace(self.workspace.id)
|
|
346
|
+
space._manager = None
|
|
347
|
+
|
|
348
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
349
|
+
space.refresh()
|
|
350
|
+
|
|
351
|
+
assert 'No workspace manager' in cm.exception.msg, cm.exception.msg
|
|
352
|
+
|
|
353
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
354
|
+
space.terminate()
|
|
355
|
+
|
|
356
|
+
assert 'No workspace manager' in cm.exception.msg, cm.exception.msg
|
|
357
|
+
|
|
358
|
+
def test_connect(self):
|
|
359
|
+
with self.workspace.connect(user='admin', password=self.password) as conn:
|
|
360
|
+
with conn.cursor() as cur:
|
|
361
|
+
cur.execute('show databases')
|
|
362
|
+
assert 'cluster' in [x[0] for x in list(cur)]
|
|
363
|
+
|
|
364
|
+
# Test missing endpoint
|
|
365
|
+
space = self.manager.get_workspace(self.workspace.id)
|
|
366
|
+
space.endpoint = None
|
|
367
|
+
|
|
368
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
369
|
+
space.connect(user='admin', password=self.password)
|
|
370
|
+
|
|
371
|
+
assert 'endpoint' in cm.exception.msg, cm.exception.msg
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@pytest.mark.management
|
|
375
|
+
class TestStarterWorkspace(unittest.TestCase):
|
|
376
|
+
|
|
377
|
+
manager = None
|
|
378
|
+
starter_workspace = None
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def setUpClass(cls):
|
|
382
|
+
cls.manager = s2.manage_workspaces()
|
|
383
|
+
|
|
384
|
+
shared_tier_regions: NamedList[Region] = [
|
|
385
|
+
x for x in cls.manager.shared_tier_regions if 'US' in x.name
|
|
386
|
+
]
|
|
387
|
+
cls.starter_username = 'starter_user'
|
|
388
|
+
cls.password = secrets.token_urlsafe(20)
|
|
389
|
+
|
|
390
|
+
name = shared_database_name(secrets.token_urlsafe(20)[:20])
|
|
391
|
+
|
|
392
|
+
cls.database_name = f'starter_db_{name}'
|
|
393
|
+
|
|
394
|
+
shared_tier_region: Region = random.choice(shared_tier_regions)
|
|
395
|
+
|
|
396
|
+
if not shared_tier_region:
|
|
397
|
+
raise ValueError('No shared tier regions found')
|
|
398
|
+
|
|
399
|
+
cls.starter_workspace = cls.manager.create_starter_workspace(
|
|
400
|
+
f'starter-ws-test-{name}',
|
|
401
|
+
database_name=cls.database_name,
|
|
402
|
+
provider=shared_tier_region.provider,
|
|
403
|
+
region_name=shared_tier_region.region_name,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
cls.starter_workspace.create_user(
|
|
407
|
+
username=cls.starter_username,
|
|
408
|
+
password=cls.password,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
@classmethod
|
|
412
|
+
def tearDownClass(cls):
|
|
413
|
+
if cls.starter_workspace is not None:
|
|
414
|
+
cls.starter_workspace.terminate()
|
|
415
|
+
cls.manager = None
|
|
416
|
+
cls.password = None
|
|
417
|
+
|
|
418
|
+
def test_str(self):
|
|
419
|
+
assert self.starter_workspace.name in str(self.starter_workspace.name)
|
|
420
|
+
|
|
421
|
+
def test_repr(self):
|
|
422
|
+
assert repr(self.starter_workspace) == str(self.starter_workspace)
|
|
423
|
+
|
|
424
|
+
def test_get_starter_workspace(self):
|
|
425
|
+
workspace = self.manager.get_starter_workspace(self.starter_workspace.id)
|
|
426
|
+
assert workspace.id == self.starter_workspace.id, workspace.id
|
|
427
|
+
|
|
428
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
429
|
+
workspace = self.manager.get_starter_workspace('bad id')
|
|
430
|
+
|
|
431
|
+
assert 'UUID' in cm.exception.msg, cm.exception.msg
|
|
432
|
+
|
|
433
|
+
def test_starter_workspaces(self):
|
|
434
|
+
workspaces = self.manager.starter_workspaces
|
|
435
|
+
ids = [x.id for x in workspaces]
|
|
436
|
+
names = [x.name for x in workspaces]
|
|
437
|
+
assert self.starter_workspace.id in ids
|
|
438
|
+
assert self.starter_workspace.name in names
|
|
439
|
+
|
|
440
|
+
objs = {}
|
|
441
|
+
for item in workspaces:
|
|
442
|
+
objs[item.id] = item
|
|
443
|
+
objs[item.name] = item
|
|
444
|
+
|
|
445
|
+
name = random.choice(names)
|
|
446
|
+
assert workspaces[name] == objs[name]
|
|
447
|
+
id = random.choice(ids)
|
|
448
|
+
assert workspaces[id] == objs[id]
|
|
449
|
+
|
|
450
|
+
def test_no_manager(self):
|
|
451
|
+
workspace = self.manager.get_starter_workspace(self.starter_workspace.id)
|
|
452
|
+
workspace._manager = None
|
|
453
|
+
|
|
454
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
455
|
+
workspace.refresh()
|
|
456
|
+
|
|
457
|
+
assert 'No workspace manager' in cm.exception.msg, cm.exception.msg
|
|
458
|
+
|
|
459
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
460
|
+
workspace.terminate()
|
|
461
|
+
|
|
462
|
+
assert 'No workspace manager' in cm.exception.msg, cm.exception.msg
|
|
463
|
+
|
|
464
|
+
def test_connect(self):
|
|
465
|
+
with self.starter_workspace.connect(
|
|
466
|
+
user=self.starter_username,
|
|
467
|
+
password=self.password,
|
|
468
|
+
) as conn:
|
|
469
|
+
with conn.cursor() as cur:
|
|
470
|
+
cur.execute('show databases')
|
|
471
|
+
assert self.database_name in [x[0] for x in list(cur)]
|
|
472
|
+
|
|
473
|
+
# Test missing endpoint
|
|
474
|
+
workspace = self.manager.get_starter_workspace(self.starter_workspace.id)
|
|
475
|
+
workspace.endpoint = None
|
|
476
|
+
|
|
477
|
+
with self.assertRaises(s2.ManagementError) as cm:
|
|
478
|
+
workspace.connect(user=self.starter_username, password=self.password)
|
|
479
|
+
|
|
480
|
+
assert 'endpoint' in cm.exception.msg, cm.exception.msg
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@pytest.mark.management
|
|
484
|
+
class TestStage(unittest.TestCase):
|
|
485
|
+
|
|
486
|
+
manager = None
|
|
487
|
+
wg = None
|
|
488
|
+
password = None
|
|
489
|
+
|
|
490
|
+
@classmethod
|
|
491
|
+
def setUpClass(cls):
|
|
492
|
+
cls.manager = s2.manage_workspaces()
|
|
493
|
+
|
|
494
|
+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
|
|
495
|
+
cls.password = secrets.token_urlsafe(20) + '-x&$'
|
|
496
|
+
|
|
497
|
+
name = clean_name(secrets.token_urlsafe(20)[:20])
|
|
498
|
+
|
|
499
|
+
cls.wg = cls.manager.create_workspace_group(
|
|
500
|
+
f'wg-test-{name}',
|
|
501
|
+
region=random.choice(us_regions).id,
|
|
502
|
+
admin_password=cls.password,
|
|
503
|
+
firewall_ranges=['0.0.0.0/0'],
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
@classmethod
|
|
507
|
+
def tearDownClass(cls):
|
|
508
|
+
if cls.wg is not None:
|
|
509
|
+
cls.wg.terminate(force=True)
|
|
510
|
+
cls.wg = None
|
|
511
|
+
cls.manager = None
|
|
512
|
+
cls.password = None
|
|
513
|
+
|
|
514
|
+
def test_upload_file(self):
|
|
515
|
+
st = self.wg.stage
|
|
516
|
+
|
|
517
|
+
upload_test_sql = f'upload_test_{id(self)}.sql'
|
|
518
|
+
upload_test2_sql = f'upload_test2_{id(self)}.sql'
|
|
519
|
+
|
|
520
|
+
root = st.info('/')
|
|
521
|
+
assert str(root.path) == '/'
|
|
522
|
+
assert root.type == 'directory'
|
|
523
|
+
|
|
524
|
+
# Upload file
|
|
525
|
+
f = st.upload_file(TEST_DIR / 'test.sql', upload_test_sql)
|
|
526
|
+
assert str(f.path) == upload_test_sql
|
|
527
|
+
assert f.type == 'file'
|
|
528
|
+
|
|
529
|
+
# Download and compare to original
|
|
530
|
+
txt = f.download(encoding='utf-8')
|
|
531
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
532
|
+
|
|
533
|
+
# Make sure we can't overwrite
|
|
534
|
+
with self.assertRaises(OSError):
|
|
535
|
+
st.upload_file(TEST_DIR / 'test.sql', upload_test_sql)
|
|
536
|
+
|
|
537
|
+
# Force overwrite with new content; use file object this time
|
|
538
|
+
f = st.upload_file(
|
|
539
|
+
open(TEST_DIR / 'test2.sql', 'r'),
|
|
540
|
+
upload_test_sql,
|
|
541
|
+
overwrite=True,
|
|
542
|
+
)
|
|
543
|
+
assert str(f.path) == upload_test_sql
|
|
544
|
+
assert f.type == 'file'
|
|
545
|
+
|
|
546
|
+
# Verify new content
|
|
547
|
+
txt = f.download(encoding='utf-8')
|
|
548
|
+
assert txt == open(TEST_DIR / 'test2.sql').read()
|
|
549
|
+
|
|
550
|
+
# Try to upload folder
|
|
551
|
+
with self.assertRaises(IsADirectoryError):
|
|
552
|
+
st.upload_file(TEST_DIR, 'test3.sql')
|
|
553
|
+
|
|
554
|
+
lib = st.mkdir('/lib/')
|
|
555
|
+
assert str(lib.path) == 'lib/'
|
|
556
|
+
assert lib.type == 'directory'
|
|
557
|
+
|
|
558
|
+
# Try to overwrite stage folder with file
|
|
559
|
+
with self.assertRaises(IsADirectoryError):
|
|
560
|
+
st.upload_file(TEST_DIR / 'test2.sql', lib.path, overwrite=True)
|
|
561
|
+
|
|
562
|
+
# Write file into folder
|
|
563
|
+
f = st.upload_file(
|
|
564
|
+
TEST_DIR / 'test2.sql',
|
|
565
|
+
os.path.join(lib.path, upload_test2_sql),
|
|
566
|
+
)
|
|
567
|
+
assert str(f.path) == 'lib/' + upload_test2_sql
|
|
568
|
+
assert f.type == 'file'
|
|
569
|
+
|
|
570
|
+
def test_open(self):
|
|
571
|
+
st = self.wg.stage
|
|
572
|
+
|
|
573
|
+
open_test_sql = f'open_test_{id(self)}.sql'
|
|
574
|
+
|
|
575
|
+
# See if error is raised for non-existent file
|
|
576
|
+
with self.assertRaises(s2.ManagementError):
|
|
577
|
+
st.open(open_test_sql, 'r')
|
|
578
|
+
|
|
579
|
+
# Load test file
|
|
580
|
+
st.upload_file(TEST_DIR / 'test.sql', open_test_sql)
|
|
581
|
+
|
|
582
|
+
# Read file using `open`
|
|
583
|
+
with st.open(open_test_sql, 'r') as rfile:
|
|
584
|
+
assert rfile.read() == open(TEST_DIR / 'test.sql').read()
|
|
585
|
+
|
|
586
|
+
# Read file using `open` with 'rt' mode
|
|
587
|
+
with st.open(open_test_sql, 'rt') as rfile:
|
|
588
|
+
assert rfile.read() == open(TEST_DIR / 'test.sql').read()
|
|
589
|
+
|
|
590
|
+
# Read file using `open` with 'rb' mode
|
|
591
|
+
with st.open(open_test_sql, 'rb') as rfile:
|
|
592
|
+
assert rfile.read() == open(TEST_DIR / 'test.sql', 'rb').read()
|
|
593
|
+
|
|
594
|
+
# Read file using `open` with 'rb' mode
|
|
595
|
+
with self.assertRaises(ValueError):
|
|
596
|
+
with st.open(open_test_sql, 'b') as rfile:
|
|
597
|
+
pass
|
|
598
|
+
|
|
599
|
+
# Attempt overwrite file using `open` with mode 'x'
|
|
600
|
+
with self.assertRaises(OSError):
|
|
601
|
+
with st.open(open_test_sql, 'x') as wfile:
|
|
602
|
+
pass
|
|
603
|
+
|
|
604
|
+
# Attempt overwrite file using `open` with mode 'w'
|
|
605
|
+
with st.open(open_test_sql, 'w') as wfile:
|
|
606
|
+
wfile.write(open(TEST_DIR / 'test2.sql').read())
|
|
607
|
+
|
|
608
|
+
txt = st.download_file(open_test_sql, encoding='utf-8')
|
|
609
|
+
|
|
610
|
+
assert txt == open(TEST_DIR / 'test2.sql').read()
|
|
611
|
+
|
|
612
|
+
open_raw_test_sql = f'open_raw_test_{id(self)}.sql'
|
|
613
|
+
|
|
614
|
+
# Test writer without context manager
|
|
615
|
+
wfile = st.open(open_raw_test_sql, 'w')
|
|
616
|
+
for line in open(TEST_DIR / 'test.sql'):
|
|
617
|
+
wfile.write(line)
|
|
618
|
+
wfile.close()
|
|
619
|
+
|
|
620
|
+
txt = st.download_file(open_raw_test_sql, encoding='utf-8')
|
|
621
|
+
|
|
622
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
623
|
+
|
|
624
|
+
# Test reader without context manager
|
|
625
|
+
rfile = st.open(open_raw_test_sql, 'r')
|
|
626
|
+
txt = ''
|
|
627
|
+
for line in rfile:
|
|
628
|
+
txt += line
|
|
629
|
+
rfile.close()
|
|
630
|
+
|
|
631
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
632
|
+
|
|
633
|
+
def test_obj_open(self):
|
|
634
|
+
st = self.wg.stage
|
|
635
|
+
|
|
636
|
+
obj_open_test_sql = f'obj_open_test_{id(self)}.sql'
|
|
637
|
+
obj_open_dir = f'obj_open_dir_{id(self)}'
|
|
638
|
+
|
|
639
|
+
# Load test file
|
|
640
|
+
f = st.upload_file(TEST_DIR / 'test.sql', obj_open_test_sql)
|
|
641
|
+
|
|
642
|
+
# Read file using `open`
|
|
643
|
+
with f.open() as rfile:
|
|
644
|
+
assert rfile.read() == open(TEST_DIR / 'test.sql').read()
|
|
645
|
+
|
|
646
|
+
# Make sure directories error out
|
|
647
|
+
d = st.mkdir(obj_open_dir)
|
|
648
|
+
with self.assertRaises(IsADirectoryError):
|
|
649
|
+
d.open()
|
|
650
|
+
|
|
651
|
+
# Write file using `open`
|
|
652
|
+
with f.open('w', encoding='utf-8') as wfile:
|
|
653
|
+
wfile.write(open(TEST_DIR / 'test2.sql').read())
|
|
654
|
+
|
|
655
|
+
assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.sql').read()
|
|
656
|
+
|
|
657
|
+
# Test writer without context manager
|
|
658
|
+
wfile = f.open('w')
|
|
659
|
+
for line in open(TEST_DIR / 'test.sql'):
|
|
660
|
+
wfile.write(line)
|
|
661
|
+
wfile.close()
|
|
662
|
+
|
|
663
|
+
txt = st.download_file(f.path, encoding='utf-8')
|
|
664
|
+
|
|
665
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
666
|
+
|
|
667
|
+
# Test reader without context manager
|
|
668
|
+
rfile = f.open('r')
|
|
669
|
+
txt = ''
|
|
670
|
+
for line in rfile:
|
|
671
|
+
txt += line
|
|
672
|
+
rfile.close()
|
|
673
|
+
|
|
674
|
+
assert txt == open(TEST_DIR / 'test.sql').read()
|
|
675
|
+
|
|
676
|
+
def test_os_directories(self):
|
|
677
|
+
st = self.wg.stage
|
|
678
|
+
|
|
679
|
+
mkdir_test_1 = f'mkdir_test_1_{id(self)}'
|
|
680
|
+
mkdir_test_2 = f'mkdir_test_2_{id(self)}'
|
|
681
|
+
mkdir_test_3 = f'mkdir_test_3_{id(self)}'
|
|
682
|
+
|
|
683
|
+
# mkdir
|
|
684
|
+
st.mkdir(mkdir_test_1)
|
|
685
|
+
st.mkdir(mkdir_test_2)
|
|
686
|
+
with self.assertRaises(s2.ManagementError):
|
|
687
|
+
st.mkdir(f'{mkdir_test_2}/nest_1/nest_2')
|
|
688
|
+
st.mkdir(f'{mkdir_test_2}/nest_1')
|
|
689
|
+
st.mkdir(f'{mkdir_test_2}/nest_1/nest_2')
|
|
690
|
+
st.mkdir(f'{mkdir_test_3}')
|
|
691
|
+
|
|
692
|
+
assert st.exists(f'{mkdir_test_1}/')
|
|
693
|
+
assert st.exists(f'{mkdir_test_2}/')
|
|
694
|
+
assert st.exists(f'{mkdir_test_2}/nest_1/')
|
|
695
|
+
assert st.exists(f'{mkdir_test_2}/nest_1/nest_2/')
|
|
696
|
+
assert not st.exists('foo/')
|
|
697
|
+
assert not st.exists('foo/bar/')
|
|
698
|
+
|
|
699
|
+
assert st.is_dir(f'{mkdir_test_1}/')
|
|
700
|
+
assert st.is_dir(f'{mkdir_test_2}/')
|
|
701
|
+
assert st.is_dir(f'{mkdir_test_2}/nest_1/')
|
|
702
|
+
assert st.is_dir(f'{mkdir_test_2}/nest_1/nest_2/')
|
|
703
|
+
|
|
704
|
+
assert not st.is_file(f'{mkdir_test_1}/')
|
|
705
|
+
assert not st.is_file(f'{mkdir_test_2}/')
|
|
706
|
+
assert not st.is_file(f'{mkdir_test_2}/nest_1/')
|
|
707
|
+
assert not st.is_file(f'{mkdir_test_2}/nest_1/nest_2/')
|
|
708
|
+
|
|
709
|
+
out = st.listdir('/')
|
|
710
|
+
assert f'{mkdir_test_1}/' in out
|
|
711
|
+
assert f'{mkdir_test_2}/' in out
|
|
712
|
+
assert f'{mkdir_test_2}/nest_1/nest_2/' not in out
|
|
713
|
+
|
|
714
|
+
out = st.listdir('/', recursive=True)
|
|
715
|
+
assert f'{mkdir_test_1}/' in out
|
|
716
|
+
assert f'{mkdir_test_2}/' in out
|
|
717
|
+
assert f'{mkdir_test_2}/nest_1/nest_2/' in out
|
|
718
|
+
|
|
719
|
+
out = st.listdir(mkdir_test_2)
|
|
720
|
+
assert f'{mkdir_test_1}/' not in out
|
|
721
|
+
assert 'nest_1/' in out
|
|
722
|
+
assert 'nest_2/' not in out
|
|
723
|
+
assert 'nest_1/nest_2/' not in out
|
|
724
|
+
|
|
725
|
+
out = st.listdir(mkdir_test_2, recursive=True)
|
|
726
|
+
assert f'{mkdir_test_1}/' not in out
|
|
727
|
+
assert 'nest_1/' in out
|
|
728
|
+
assert 'nest_2/' not in out
|
|
729
|
+
assert 'nest_1/nest_2/' in out
|
|
730
|
+
|
|
731
|
+
# rmdir
|
|
732
|
+
before = st.listdir('/', recursive=True)
|
|
733
|
+
st.rmdir(f'{mkdir_test_1}/')
|
|
734
|
+
after = st.listdir('/', recursive=True)
|
|
735
|
+
assert f'{mkdir_test_1}/' in before
|
|
736
|
+
assert f'{mkdir_test_1}/' not in after
|
|
737
|
+
assert list(sorted(before)) == list(sorted(after + [f'{mkdir_test_1}/']))
|
|
738
|
+
|
|
739
|
+
with self.assertRaises(OSError):
|
|
740
|
+
st.rmdir(f'{mkdir_test_2}/')
|
|
741
|
+
|
|
742
|
+
mkdir_test_sql = f'mkdir_test_{id(self)}.sql'
|
|
743
|
+
|
|
744
|
+
st.upload_file(TEST_DIR / 'test.sql', mkdir_test_sql)
|
|
745
|
+
|
|
746
|
+
with self.assertRaises(NotADirectoryError):
|
|
747
|
+
st.rmdir(mkdir_test_sql)
|
|
748
|
+
|
|
749
|
+
# removedirs
|
|
750
|
+
before = st.listdir('/')
|
|
751
|
+
st.removedirs(f'{mkdir_test_2}/')
|
|
752
|
+
after = st.listdir('/')
|
|
753
|
+
assert f'{mkdir_test_2}/' in before
|
|
754
|
+
assert f'{mkdir_test_2}/' not in after
|
|
755
|
+
assert list(sorted(before)) == list(sorted(after + [f'{mkdir_test_2}/']))
|
|
756
|
+
|
|
757
|
+
with self.assertRaises(s2.ManagementError):
|
|
758
|
+
st.removedirs(mkdir_test_sql)
|
|
759
|
+
|
|
760
|
+
def test_os_files(self):
|
|
761
|
+
st = self.wg.stage
|
|
762
|
+
|
|
763
|
+
files_test_sql = f'files_test_{id(self)}.sql'
|
|
764
|
+
files_test_1_dir = f'files_test_1_{id(self)}'
|
|
765
|
+
|
|
766
|
+
st.mkdir(files_test_1_dir)
|
|
767
|
+
st.mkdir(f'{files_test_1_dir}/nest_1')
|
|
768
|
+
|
|
769
|
+
st.upload_file(TEST_DIR / 'test.sql', files_test_sql)
|
|
770
|
+
st.upload_file(
|
|
771
|
+
TEST_DIR / 'test.sql',
|
|
772
|
+
f'{files_test_1_dir}/nest_1/nested_files_test.sql',
|
|
773
|
+
)
|
|
774
|
+
st.upload_file(
|
|
775
|
+
TEST_DIR / 'test.sql',
|
|
776
|
+
f'{files_test_1_dir}/nest_1/nested_files_test_2.sql',
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# remove
|
|
780
|
+
with self.assertRaises(IsADirectoryError):
|
|
781
|
+
st.remove(f'{files_test_1_dir}/')
|
|
782
|
+
|
|
783
|
+
before = st.listdir('/')
|
|
784
|
+
st.remove(files_test_sql)
|
|
785
|
+
after = st.listdir('/')
|
|
786
|
+
assert files_test_sql in before
|
|
787
|
+
assert files_test_sql not in after
|
|
788
|
+
assert list(sorted(before)) == list(sorted(after + [files_test_sql]))
|
|
789
|
+
|
|
790
|
+
before = st.listdir(f'{files_test_1_dir}/nest_1/')
|
|
791
|
+
st.remove(f'{files_test_1_dir}/nest_1/nested_files_test.sql')
|
|
792
|
+
after = st.listdir(f'{files_test_1_dir}/nest_1/')
|
|
793
|
+
assert 'nested_files_test.sql' in before
|
|
794
|
+
assert 'nested_files_test.sql' not in after
|
|
795
|
+
assert st.is_dir(f'{files_test_1_dir}/nest_1/')
|
|
796
|
+
|
|
797
|
+
# Removing the last file does not remove empty directories
|
|
798
|
+
st.remove(f'{files_test_1_dir}/nest_1/nested_files_test_2.sql')
|
|
799
|
+
assert not st.is_file(f'{files_test_1_dir}/nest_1/nested_files_test_2.sql')
|
|
800
|
+
assert st.is_dir(f'{files_test_1_dir}/nest_1/')
|
|
801
|
+
assert st.is_dir(f'{files_test_1_dir}/')
|
|
802
|
+
|
|
803
|
+
st.removedirs(files_test_1_dir)
|
|
804
|
+
assert not st.is_dir(f'{files_test_1_dir}/nest_1/')
|
|
805
|
+
assert not st.is_dir(f'{files_test_1_dir}/')
|
|
806
|
+
|
|
807
|
+
def test_os_rename(self):
|
|
808
|
+
st = self.wg.stage
|
|
809
|
+
|
|
810
|
+
rename_test_sql = f'rename_test_{id(self)}.sql'
|
|
811
|
+
rename_test_2_sql = f'rename_test_2_{id(self)}.sql'
|
|
812
|
+
rename_test_1_dir = f'rename_test_1_{id(self)}'
|
|
813
|
+
rename_test_2_dir = f'rename_test_2_{id(self)}'
|
|
814
|
+
|
|
815
|
+
st.upload_file(TEST_DIR / 'test.sql', rename_test_sql)
|
|
816
|
+
|
|
817
|
+
with self.assertRaises(s2.ManagementError):
|
|
818
|
+
st.upload_file(
|
|
819
|
+
TEST_DIR / 'test.sql',
|
|
820
|
+
f'{rename_test_1_dir}/nest_1/nested_rename_test.sql',
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
st.mkdir(rename_test_1_dir)
|
|
824
|
+
st.mkdir(f'{rename_test_1_dir}/nest_1')
|
|
825
|
+
|
|
826
|
+
assert st.exists(f'/{rename_test_1_dir}/nest_1/')
|
|
827
|
+
|
|
828
|
+
st.upload_file(
|
|
829
|
+
TEST_DIR / 'test.sql',
|
|
830
|
+
f'{rename_test_1_dir}/nest_1/nested_rename_test.sql',
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
st.upload_file(
|
|
834
|
+
TEST_DIR / 'test.sql',
|
|
835
|
+
f'{rename_test_1_dir}/nest_1/nested_rename_test_2.sql',
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
# rename file
|
|
839
|
+
assert rename_test_sql in st.listdir('/')
|
|
840
|
+
assert rename_test_2_sql not in st.listdir('/')
|
|
841
|
+
st.rename(rename_test_sql, rename_test_2_sql)
|
|
842
|
+
assert rename_test_sql not in st.listdir('/')
|
|
843
|
+
assert rename_test_2_sql in st.listdir('/')
|
|
844
|
+
|
|
845
|
+
# rename directory
|
|
846
|
+
assert f'{rename_test_1_dir}/' in st.listdir('/')
|
|
847
|
+
assert f'{rename_test_2_dir}/' not in st.listdir('/')
|
|
848
|
+
st.rename(f'{rename_test_1_dir}/', f'{rename_test_2_dir}/')
|
|
849
|
+
assert f'{rename_test_1_dir}/' not in st.listdir('/')
|
|
850
|
+
assert f'{rename_test_2_dir}/' in st.listdir('/')
|
|
851
|
+
assert st.is_file(f'{rename_test_2_dir}/nest_1/nested_rename_test.sql')
|
|
852
|
+
assert st.is_file(f'{rename_test_2_dir}/nest_1/nested_rename_test_2.sql')
|
|
853
|
+
|
|
854
|
+
# rename nested
|
|
855
|
+
assert f'{rename_test_2_dir}/nest_1/nested_rename_test.sql' in st.listdir(
|
|
856
|
+
'/', recursive=True,
|
|
857
|
+
)
|
|
858
|
+
assert f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql' not in st.listdir(
|
|
859
|
+
'/', recursive=True,
|
|
860
|
+
)
|
|
861
|
+
st.rename(
|
|
862
|
+
f'{rename_test_2_dir}/nest_1/nested_rename_test.sql',
|
|
863
|
+
f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql',
|
|
864
|
+
)
|
|
865
|
+
assert f'{rename_test_2_dir}/nest_1/nested_rename_test.sql' not in st.listdir(
|
|
866
|
+
'/', recursive=True,
|
|
867
|
+
)
|
|
868
|
+
assert f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql' in st.listdir(
|
|
869
|
+
'/', recursive=True,
|
|
870
|
+
)
|
|
871
|
+
assert not st.is_file(f'{rename_test_2_dir}/nest_1/nested_rename_test.sql')
|
|
872
|
+
assert st.is_file(f'{rename_test_2_dir}/nest_1/nested_rename_test_2.sql')
|
|
873
|
+
assert st.is_file(f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql')
|
|
874
|
+
|
|
875
|
+
# non-existent file
|
|
876
|
+
with self.assertRaises(OSError):
|
|
877
|
+
st.rename('rename_foo.sql', 'rename_foo_2.sql')
|
|
878
|
+
|
|
879
|
+
# overwrite
|
|
880
|
+
with self.assertRaises(OSError):
|
|
881
|
+
st.rename(
|
|
882
|
+
rename_test_2_sql,
|
|
883
|
+
f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql',
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
st.rename(
|
|
887
|
+
rename_test_2_sql,
|
|
888
|
+
f'{rename_test_2_dir}/nest_1/nested_rename_test_3.sql', overwrite=True,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def test_file_object(self):
|
|
892
|
+
st = self.wg.stage
|
|
893
|
+
|
|
894
|
+
obj_test_dir = f'obj_test_{id(self)}'
|
|
895
|
+
|
|
896
|
+
st.mkdir(obj_test_dir)
|
|
897
|
+
st.mkdir(f'{obj_test_dir}/nest_1')
|
|
898
|
+
|
|
899
|
+
obj_test_sql = f'obj_test_{id(self)}.sql'
|
|
900
|
+
obj_test_2_sql = f'obj_test_2_{id(self)}.sql'
|
|
901
|
+
|
|
902
|
+
f1 = st.upload_file(TEST_DIR / 'test.sql', obj_test_sql)
|
|
903
|
+
f2 = st.upload_file(
|
|
904
|
+
TEST_DIR / 'test.sql',
|
|
905
|
+
f'{obj_test_dir}/nest_1/{obj_test_sql}',
|
|
906
|
+
)
|
|
907
|
+
d2 = st.info(f'{obj_test_dir}/nest_1/')
|
|
908
|
+
|
|
909
|
+
# is_file / is_dir
|
|
910
|
+
assert not f1.is_dir()
|
|
911
|
+
assert f1.is_file()
|
|
912
|
+
assert not f2.is_dir()
|
|
913
|
+
assert f2.is_file()
|
|
914
|
+
assert d2.is_dir()
|
|
915
|
+
assert not d2.is_file()
|
|
916
|
+
|
|
917
|
+
# abspath / basename / dirname / exists
|
|
918
|
+
assert f1.abspath() == obj_test_sql
|
|
919
|
+
assert f1.basename() == obj_test_sql
|
|
920
|
+
assert f1.dirname() == '/'
|
|
921
|
+
assert f1.exists()
|
|
922
|
+
assert f2.abspath() == f'{obj_test_dir}/nest_1/{obj_test_sql}'
|
|
923
|
+
assert f2.basename() == obj_test_sql
|
|
924
|
+
assert f2.dirname() == f'{obj_test_dir}/nest_1/'
|
|
925
|
+
assert f2.exists()
|
|
926
|
+
assert d2.abspath() == f'{obj_test_dir}/nest_1/'
|
|
927
|
+
assert d2.basename() == 'nest_1'
|
|
928
|
+
assert d2.dirname() == f'{obj_test_dir}/'
|
|
929
|
+
assert d2.exists()
|
|
930
|
+
|
|
931
|
+
# download
|
|
932
|
+
assert f1.download(encoding='utf-8') == open(TEST_DIR / 'test.sql', 'r').read()
|
|
933
|
+
assert f1.download() == open(TEST_DIR / 'test.sql', 'rb').read()
|
|
934
|
+
|
|
935
|
+
# remove
|
|
936
|
+
with self.assertRaises(IsADirectoryError):
|
|
937
|
+
d2.remove()
|
|
938
|
+
|
|
939
|
+
assert st.is_file(obj_test_sql)
|
|
940
|
+
f1.remove()
|
|
941
|
+
assert not st.is_file(obj_test_sql)
|
|
942
|
+
|
|
943
|
+
# removedirs
|
|
944
|
+
with self.assertRaises(NotADirectoryError):
|
|
945
|
+
f2.removedirs()
|
|
946
|
+
|
|
947
|
+
assert st.exists(d2.path)
|
|
948
|
+
d2.removedirs()
|
|
949
|
+
assert not st.exists(d2.path)
|
|
950
|
+
|
|
951
|
+
# rmdir
|
|
952
|
+
f1 = st.upload_file(TEST_DIR / 'test.sql', obj_test_sql)
|
|
953
|
+
d2 = st.mkdir(f'{obj_test_dir}/nest_1')
|
|
954
|
+
|
|
955
|
+
assert st.exists(f1.path)
|
|
956
|
+
assert st.exists(d2.path)
|
|
957
|
+
|
|
958
|
+
with self.assertRaises(NotADirectoryError):
|
|
959
|
+
f1.rmdir()
|
|
960
|
+
|
|
961
|
+
assert st.exists(f1.path)
|
|
962
|
+
assert st.exists(d2.path)
|
|
963
|
+
|
|
964
|
+
d2.rmdir()
|
|
965
|
+
|
|
966
|
+
assert not st.exists(f'{obj_test_dir}/nest_1/')
|
|
967
|
+
assert not st.exists(obj_test_dir)
|
|
968
|
+
|
|
969
|
+
# mtime / ctime
|
|
970
|
+
assert f1.getmtime() > 0
|
|
971
|
+
assert f1.getctime() > 0
|
|
972
|
+
|
|
973
|
+
# rename
|
|
974
|
+
assert st.exists(obj_test_sql)
|
|
975
|
+
assert not st.exists(obj_test_2_sql)
|
|
976
|
+
f1.rename(obj_test_2_sql)
|
|
977
|
+
assert not st.exists(obj_test_sql)
|
|
978
|
+
assert st.exists(obj_test_2_sql)
|
|
979
|
+
assert f1.abspath() == obj_test_2_sql
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
@pytest.mark.management
|
|
983
|
+
class TestSecrets(unittest.TestCase):
|
|
984
|
+
|
|
985
|
+
manager = None
|
|
986
|
+
wg = None
|
|
987
|
+
password = None
|
|
988
|
+
|
|
989
|
+
@classmethod
|
|
990
|
+
def setUpClass(cls):
|
|
991
|
+
cls.manager = s2.manage_workspaces()
|
|
992
|
+
|
|
993
|
+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
|
|
994
|
+
cls.password = secrets.token_urlsafe(20) + '-x&$'
|
|
995
|
+
|
|
996
|
+
name = clean_name(secrets.token_urlsafe(20)[:20])
|
|
997
|
+
|
|
998
|
+
cls.wg = cls.manager.create_workspace_group(
|
|
999
|
+
f'wg-test-{name}',
|
|
1000
|
+
region=random.choice(us_regions).id,
|
|
1001
|
+
admin_password=cls.password,
|
|
1002
|
+
firewall_ranges=['0.0.0.0/0'],
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
@classmethod
|
|
1006
|
+
def tearDownClass(cls):
|
|
1007
|
+
if cls.wg is not None:
|
|
1008
|
+
cls.wg.terminate(force=True)
|
|
1009
|
+
cls.wg = None
|
|
1010
|
+
cls.manager = None
|
|
1011
|
+
cls.password = None
|
|
1012
|
+
|
|
1013
|
+
def test_get_secret(self):
|
|
1014
|
+
# manually create secret and then get secret
|
|
1015
|
+
# try to delete the secret if it exists
|
|
1016
|
+
try:
|
|
1017
|
+
secret = self.manager.organizations.current.get_secret('secret_name')
|
|
1018
|
+
|
|
1019
|
+
secret_id = secret.id
|
|
1020
|
+
|
|
1021
|
+
self.manager._delete(f'secrets/{secret_id}')
|
|
1022
|
+
except s2.ManagementError:
|
|
1023
|
+
pass
|
|
1024
|
+
|
|
1025
|
+
self.manager._post(
|
|
1026
|
+
'secrets',
|
|
1027
|
+
json=dict(
|
|
1028
|
+
name='secret_name',
|
|
1029
|
+
value='secret_value',
|
|
1030
|
+
),
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
secret = self.manager.organizations.current.get_secret('secret_name')
|
|
1034
|
+
|
|
1035
|
+
assert secret.name == 'secret_name'
|
|
1036
|
+
assert secret.value == 'secret_value'
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
@pytest.mark.management
|
|
1040
|
+
class TestJob(unittest.TestCase):
|
|
1041
|
+
|
|
1042
|
+
manager = None
|
|
1043
|
+
workspace_group = None
|
|
1044
|
+
workspace = None
|
|
1045
|
+
password = None
|
|
1046
|
+
job_ids = []
|
|
1047
|
+
|
|
1048
|
+
@classmethod
|
|
1049
|
+
def setUpClass(cls):
|
|
1050
|
+
cls.manager = s2.manage_workspaces()
|
|
1051
|
+
|
|
1052
|
+
us_regions = [x for x in cls.manager.regions if 'US' in x.name]
|
|
1053
|
+
cls.password = secrets.token_urlsafe(20) + '-x&$'
|
|
1054
|
+
|
|
1055
|
+
name = clean_name(secrets.token_urlsafe(20)[:20])
|
|
1056
|
+
|
|
1057
|
+
cls.workspace_group = cls.manager.create_workspace_group(
|
|
1058
|
+
f'wg-test-{name}',
|
|
1059
|
+
region=random.choice(us_regions).id,
|
|
1060
|
+
admin_password=cls.password,
|
|
1061
|
+
firewall_ranges=['0.0.0.0/0'],
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
try:
|
|
1065
|
+
cls.workspace = cls.workspace_group.create_workspace(
|
|
1066
|
+
f'ws-test-{name}-x',
|
|
1067
|
+
wait_on_active=True,
|
|
1068
|
+
)
|
|
1069
|
+
except Exception:
|
|
1070
|
+
cls.workspace_group.terminate(force=True)
|
|
1071
|
+
raise
|
|
1072
|
+
|
|
1073
|
+
@classmethod
|
|
1074
|
+
def tearDownClass(cls):
|
|
1075
|
+
for job_id in cls.job_ids:
|
|
1076
|
+
try:
|
|
1077
|
+
cls.manager.organizations.current.jobs.delete(job_id)
|
|
1078
|
+
except Exception:
|
|
1079
|
+
pass
|
|
1080
|
+
if cls.workspace_group is not None:
|
|
1081
|
+
cls.workspace_group.terminate(force=True)
|
|
1082
|
+
cls.workspace_group = None
|
|
1083
|
+
cls.workspace = None
|
|
1084
|
+
cls.manager = None
|
|
1085
|
+
cls.password = None
|
|
1086
|
+
if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:
|
|
1087
|
+
del os.environ['SINGLESTOREDB_WORKSPACE']
|
|
1088
|
+
if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:
|
|
1089
|
+
del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']
|
|
1090
|
+
|
|
1091
|
+
def test_job_without_database_target(self):
|
|
1092
|
+
"""
|
|
1093
|
+
Creates job without target database on a specific runtime
|
|
1094
|
+
Waits for job to finish
|
|
1095
|
+
Gets the job
|
|
1096
|
+
Deletes the job
|
|
1097
|
+
"""
|
|
1098
|
+
if os.environ.get('SINGLESTOREDB_WORKSPACE', None) is not None:
|
|
1099
|
+
del os.environ['SINGLESTOREDB_WORKSPACE']
|
|
1100
|
+
if os.environ.get('SINGLESTOREDB_DEFAULT_DATABASE', None) is not None:
|
|
1101
|
+
del os.environ['SINGLESTOREDB_DEFAULT_DATABASE']
|
|
1102
|
+
|
|
1103
|
+
job_manager = self.manager.organizations.current.jobs
|
|
1104
|
+
job = job_manager.run(
|
|
1105
|
+
'Scheduling Test.ipynb',
|
|
1106
|
+
'notebooks-cpu-small',
|
|
1107
|
+
{'strParam': 'string', 'intParam': 1, 'floatParam': 1.0, 'boolParam': True},
|
|
1108
|
+
)
|
|
1109
|
+
self.job_ids.append(job.job_id)
|
|
1110
|
+
assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'
|
|
1111
|
+
assert job.schedule.mode == job_manager.modes().ONCE
|
|
1112
|
+
assert not job.execution_config.create_snapshot
|
|
1113
|
+
assert job.completed_executions_count == 0
|
|
1114
|
+
assert job.name is None
|
|
1115
|
+
assert job.description is None
|
|
1116
|
+
assert job.job_metadata == []
|
|
1117
|
+
assert job.terminated_at is None
|
|
1118
|
+
assert job.target_config is None
|
|
1119
|
+
job.wait()
|
|
1120
|
+
job = job_manager.get(job.job_id)
|
|
1121
|
+
assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'
|
|
1122
|
+
assert job.schedule.mode == job_manager.modes().ONCE
|
|
1123
|
+
assert not job.execution_config.create_snapshot
|
|
1124
|
+
assert job.completed_executions_count == 1
|
|
1125
|
+
assert job.name is None
|
|
1126
|
+
assert job.description is None
|
|
1127
|
+
assert job.job_metadata != []
|
|
1128
|
+
assert len(job.job_metadata) == 1
|
|
1129
|
+
assert job.job_metadata[0].count == 1
|
|
1130
|
+
assert job.job_metadata[0].status == Status.COMPLETED
|
|
1131
|
+
assert job.terminated_at is None
|
|
1132
|
+
assert job.target_config is None
|
|
1133
|
+
deleted = job.delete()
|
|
1134
|
+
assert deleted
|
|
1135
|
+
job = job_manager.get(job.job_id)
|
|
1136
|
+
assert job.terminated_at is not None
|
|
1137
|
+
|
|
1138
|
+
def test_job_with_database_target(self):
|
|
1139
|
+
"""
|
|
1140
|
+
Creates job with target database on a specific runtime
|
|
1141
|
+
Waits for job to finish
|
|
1142
|
+
Gets the job
|
|
1143
|
+
Deletes the job
|
|
1144
|
+
"""
|
|
1145
|
+
os.environ['SINGLESTOREDB_DEFAULT_DATABASE'] = 'information_schema'
|
|
1146
|
+
os.environ['SINGLESTOREDB_WORKSPACE'] = self.workspace.id
|
|
1147
|
+
|
|
1148
|
+
job_manager = self.manager.organizations.current.jobs
|
|
1149
|
+
job = job_manager.run(
|
|
1150
|
+
'Scheduling Test.ipynb',
|
|
1151
|
+
'notebooks-cpu-small',
|
|
1152
|
+
{'strParam': 'string', 'intParam': 1, 'floatParam': 1.0, 'boolParam': True},
|
|
1153
|
+
)
|
|
1154
|
+
self.job_ids.append(job.job_id)
|
|
1155
|
+
assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'
|
|
1156
|
+
assert job.schedule.mode == job_manager.modes().ONCE
|
|
1157
|
+
assert not job.execution_config.create_snapshot
|
|
1158
|
+
assert job.completed_executions_count == 0
|
|
1159
|
+
assert job.name is None
|
|
1160
|
+
assert job.description is None
|
|
1161
|
+
assert job.job_metadata == []
|
|
1162
|
+
assert job.terminated_at is None
|
|
1163
|
+
assert job.target_config is not None
|
|
1164
|
+
assert job.target_config.database_name == 'information_schema'
|
|
1165
|
+
assert job.target_config.target_id == self.workspace.id
|
|
1166
|
+
assert job.target_config.target_type == TargetType.WORKSPACE
|
|
1167
|
+
assert not job.target_config.resume_target
|
|
1168
|
+
job.wait()
|
|
1169
|
+
job = job_manager.get(job.job_id)
|
|
1170
|
+
assert job.execution_config.notebook_path == 'Scheduling Test.ipynb'
|
|
1171
|
+
assert job.schedule.mode == job_manager.modes().ONCE
|
|
1172
|
+
assert not job.execution_config.create_snapshot
|
|
1173
|
+
assert job.completed_executions_count == 1
|
|
1174
|
+
assert job.name is None
|
|
1175
|
+
assert job.description is None
|
|
1176
|
+
assert job.job_metadata != []
|
|
1177
|
+
assert len(job.job_metadata) == 1
|
|
1178
|
+
assert job.job_metadata[0].count == 1
|
|
1179
|
+
assert job.job_metadata[0].status == Status.COMPLETED
|
|
1180
|
+
assert job.terminated_at is None
|
|
1181
|
+
assert job.target_config is not None
|
|
1182
|
+
assert job.target_config.database_name == 'information_schema'
|
|
1183
|
+
assert job.target_config.target_id == self.workspace.id
|
|
1184
|
+
assert job.target_config.target_type == TargetType.WORKSPACE
|
|
1185
|
+
assert not job.target_config.resume_target
|
|
1186
|
+
deleted = job.delete()
|
|
1187
|
+
assert deleted
|
|
1188
|
+
job = job_manager.get(job.job_id)
|
|
1189
|
+
assert job.terminated_at is not None
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
@pytest.mark.management
|
|
1193
|
+
class TestFileSpaces(unittest.TestCase):
|
|
1194
|
+
|
|
1195
|
+
manager = None
|
|
1196
|
+
personal_space = None
|
|
1197
|
+
shared_space = None
|
|
1198
|
+
|
|
1199
|
+
@classmethod
|
|
1200
|
+
def setUpClass(cls):
|
|
1201
|
+
cls.manager = s2.manage_files()
|
|
1202
|
+
cls.personal_space = cls.manager.personal_space
|
|
1203
|
+
cls.shared_space = cls.manager.shared_space
|
|
1204
|
+
|
|
1205
|
+
@classmethod
|
|
1206
|
+
def tearDownClass(cls):
|
|
1207
|
+
cls.manager = None
|
|
1208
|
+
cls.personal_space = None
|
|
1209
|
+
cls.shared_space = None
|
|
1210
|
+
|
|
1211
|
+
def test_upload_file(self):
|
|
1212
|
+
upload_test_ipynb = f'upload_test_{id(self)}.ipynb'
|
|
1213
|
+
|
|
1214
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1215
|
+
root = space.info('/')
|
|
1216
|
+
assert str(root.path) == '/'
|
|
1217
|
+
assert root.type == 'directory'
|
|
1218
|
+
|
|
1219
|
+
# Upload files
|
|
1220
|
+
f = space.upload_file(
|
|
1221
|
+
TEST_DIR / 'test.ipynb',
|
|
1222
|
+
upload_test_ipynb,
|
|
1223
|
+
)
|
|
1224
|
+
assert str(f.path) == upload_test_ipynb
|
|
1225
|
+
assert f.type == 'notebook'
|
|
1226
|
+
|
|
1227
|
+
# Download and compare to original
|
|
1228
|
+
txt = f.download(encoding='utf-8')
|
|
1229
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1230
|
+
|
|
1231
|
+
# Make sure we can't overwrite
|
|
1232
|
+
with self.assertRaises(OSError):
|
|
1233
|
+
space.upload_file(
|
|
1234
|
+
TEST_DIR / 'test.ipynb',
|
|
1235
|
+
upload_test_ipynb,
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
# Force overwrite with new content
|
|
1239
|
+
f = space.upload_file(
|
|
1240
|
+
TEST_DIR / 'test2.ipynb',
|
|
1241
|
+
upload_test_ipynb, overwrite=True,
|
|
1242
|
+
)
|
|
1243
|
+
assert str(f.path) == upload_test_ipynb
|
|
1244
|
+
assert f.type == 'notebook'
|
|
1245
|
+
|
|
1246
|
+
# Verify new content
|
|
1247
|
+
txt = f.download(encoding='utf-8')
|
|
1248
|
+
assert txt == open(TEST_DIR / 'test2.ipynb').read()
|
|
1249
|
+
|
|
1250
|
+
# Make sure we can't upload a folder
|
|
1251
|
+
with self.assertRaises(s2.ManagementError):
|
|
1252
|
+
space.upload_folder(TEST_DIR, 'test')
|
|
1253
|
+
|
|
1254
|
+
# Cleanup
|
|
1255
|
+
space.remove(upload_test_ipynb)
|
|
1256
|
+
|
|
1257
|
+
def test_upload_file_io(self):
|
|
1258
|
+
upload_test_ipynb = f'upload_test_{id(self)}.ipynb'
|
|
1259
|
+
|
|
1260
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1261
|
+
root = space.info('/')
|
|
1262
|
+
assert str(root.path) == '/'
|
|
1263
|
+
assert root.type == 'directory'
|
|
1264
|
+
|
|
1265
|
+
# Upload files
|
|
1266
|
+
f = space.upload_file(
|
|
1267
|
+
open(TEST_DIR / 'test.ipynb', 'r'),
|
|
1268
|
+
upload_test_ipynb,
|
|
1269
|
+
)
|
|
1270
|
+
assert str(f.path) == upload_test_ipynb
|
|
1271
|
+
assert f.type == 'notebook'
|
|
1272
|
+
|
|
1273
|
+
# Download and compare to original
|
|
1274
|
+
txt = f.download(encoding='utf-8')
|
|
1275
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1276
|
+
|
|
1277
|
+
# Make sure we can't overwrite
|
|
1278
|
+
with self.assertRaises(OSError):
|
|
1279
|
+
space.upload_file(
|
|
1280
|
+
open(TEST_DIR / 'test.ipynb', 'r'),
|
|
1281
|
+
upload_test_ipynb,
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
# Force overwrite with new content
|
|
1285
|
+
f = space.upload_file(
|
|
1286
|
+
open(TEST_DIR / 'test2.ipynb', 'r'),
|
|
1287
|
+
upload_test_ipynb, overwrite=True,
|
|
1288
|
+
)
|
|
1289
|
+
assert str(f.path) == upload_test_ipynb
|
|
1290
|
+
assert f.type == 'notebook'
|
|
1291
|
+
|
|
1292
|
+
# Verify new content
|
|
1293
|
+
txt = f.download(encoding='utf-8')
|
|
1294
|
+
assert txt == open(TEST_DIR / 'test2.ipynb').read()
|
|
1295
|
+
|
|
1296
|
+
# Make sure we can't upload a folder
|
|
1297
|
+
with self.assertRaises(s2.ManagementError):
|
|
1298
|
+
space.upload_folder(TEST_DIR, 'test')
|
|
1299
|
+
|
|
1300
|
+
# Cleanup
|
|
1301
|
+
space.remove(upload_test_ipynb)
|
|
1302
|
+
|
|
1303
|
+
def test_open(self):
|
|
1304
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1305
|
+
open_test_ipynb = f'open_test_ipynb_{id(self)}.ipynb'
|
|
1306
|
+
|
|
1307
|
+
# See if error is raised for non-existent file
|
|
1308
|
+
with self.assertRaises(s2.ManagementError):
|
|
1309
|
+
space.open(open_test_ipynb, 'r')
|
|
1310
|
+
|
|
1311
|
+
# Load test file
|
|
1312
|
+
space.upload_file(TEST_DIR / 'test.ipynb', open_test_ipynb)
|
|
1313
|
+
|
|
1314
|
+
# Read file using `open`
|
|
1315
|
+
with space.open(open_test_ipynb, 'r') as rfile:
|
|
1316
|
+
assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
|
|
1317
|
+
|
|
1318
|
+
# Read file using `open` with 'rt' mode
|
|
1319
|
+
with space.open(open_test_ipynb, 'rt') as rfile:
|
|
1320
|
+
assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
|
|
1321
|
+
|
|
1322
|
+
# Read file using `open` with 'rb' mode
|
|
1323
|
+
with space.open(open_test_ipynb, 'rb') as rfile:
|
|
1324
|
+
assert rfile.read() == open(TEST_DIR / 'test.ipynb', 'rb').read()
|
|
1325
|
+
|
|
1326
|
+
# Read file using `open` with 'rb' mode
|
|
1327
|
+
with self.assertRaises(ValueError):
|
|
1328
|
+
with space.open(open_test_ipynb, 'b') as rfile:
|
|
1329
|
+
pass
|
|
1330
|
+
|
|
1331
|
+
# Attempt overwrite file using `open` with mode 'x'
|
|
1332
|
+
with self.assertRaises(OSError):
|
|
1333
|
+
with space.open(open_test_ipynb, 'x') as wfile:
|
|
1334
|
+
pass
|
|
1335
|
+
|
|
1336
|
+
# Attempt overwrite file using `open` with mode 'w'
|
|
1337
|
+
with space.open(open_test_ipynb, 'w') as wfile:
|
|
1338
|
+
wfile.write(open(TEST_DIR / 'test2.ipynb').read())
|
|
1339
|
+
|
|
1340
|
+
txt = space.download_file(open_test_ipynb, encoding='utf-8')
|
|
1341
|
+
|
|
1342
|
+
assert txt == open(TEST_DIR / 'test2.ipynb').read()
|
|
1343
|
+
|
|
1344
|
+
open_raw_test_ipynb = f'open_raw_test_{id(self)}.ipynb'
|
|
1345
|
+
|
|
1346
|
+
# Test writer without context manager
|
|
1347
|
+
wfile = space.open(open_raw_test_ipynb, 'w')
|
|
1348
|
+
for line in open(TEST_DIR / 'test.ipynb'):
|
|
1349
|
+
wfile.write(line)
|
|
1350
|
+
wfile.close()
|
|
1351
|
+
|
|
1352
|
+
txt = space.download_file(
|
|
1353
|
+
open_raw_test_ipynb,
|
|
1354
|
+
encoding='utf-8',
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1358
|
+
|
|
1359
|
+
# Test reader without context manager
|
|
1360
|
+
rfile = space.open(open_raw_test_ipynb, 'r')
|
|
1361
|
+
txt = ''
|
|
1362
|
+
for line in rfile:
|
|
1363
|
+
txt += line
|
|
1364
|
+
rfile.close()
|
|
1365
|
+
|
|
1366
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1367
|
+
|
|
1368
|
+
# Cleanup
|
|
1369
|
+
space.remove(open_test_ipynb)
|
|
1370
|
+
space.remove(open_raw_test_ipynb)
|
|
1371
|
+
|
|
1372
|
+
def test_obj_open(self):
|
|
1373
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1374
|
+
obj_open_test_ipynb = f'obj_open_test_{id(self)}.ipynb'
|
|
1375
|
+
obj_open_dir = f'obj_open_dir_{id(self)}'
|
|
1376
|
+
|
|
1377
|
+
# Load test file
|
|
1378
|
+
f = space.upload_file(
|
|
1379
|
+
TEST_DIR / 'test.ipynb',
|
|
1380
|
+
obj_open_test_ipynb,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
# Read file using `open`
|
|
1384
|
+
with f.open() as rfile:
|
|
1385
|
+
assert rfile.read() == open(TEST_DIR / 'test.ipynb').read()
|
|
1386
|
+
|
|
1387
|
+
# Make sure directories error out
|
|
1388
|
+
with self.assertRaises(s2.ManagementError):
|
|
1389
|
+
space.mkdir(obj_open_dir)
|
|
1390
|
+
|
|
1391
|
+
# Write file using `open`
|
|
1392
|
+
with f.open('w', encoding='utf-8') as wfile:
|
|
1393
|
+
wfile.write(open(TEST_DIR / 'test2.ipynb').read())
|
|
1394
|
+
|
|
1395
|
+
assert f.download(encoding='utf-8') == open(TEST_DIR / 'test2.ipynb').read()
|
|
1396
|
+
|
|
1397
|
+
# Test writer without context manager
|
|
1398
|
+
wfile = f.open('w')
|
|
1399
|
+
for line in open(TEST_DIR / 'test.ipynb'):
|
|
1400
|
+
wfile.write(line)
|
|
1401
|
+
wfile.close()
|
|
1402
|
+
|
|
1403
|
+
txt = space.download_file(f.path, encoding='utf-8')
|
|
1404
|
+
|
|
1405
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1406
|
+
|
|
1407
|
+
# Test reader without context manager
|
|
1408
|
+
rfile = f.open('r')
|
|
1409
|
+
txt = ''
|
|
1410
|
+
for line in rfile:
|
|
1411
|
+
txt += line
|
|
1412
|
+
rfile.close()
|
|
1413
|
+
|
|
1414
|
+
assert txt == open(TEST_DIR / 'test.ipynb').read()
|
|
1415
|
+
|
|
1416
|
+
# Cleanup
|
|
1417
|
+
space.remove(obj_open_test_ipynb)
|
|
1418
|
+
|
|
1419
|
+
def test_os_directories(self):
|
|
1420
|
+
mkdir_test_1_dir = f'mkdir_test_1_{id(self)}'
|
|
1421
|
+
|
|
1422
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1423
|
+
# Make sure directories error out
|
|
1424
|
+
with self.assertRaises(s2.ManagementError):
|
|
1425
|
+
space.mkdir(mkdir_test_1_dir)
|
|
1426
|
+
|
|
1427
|
+
with self.assertRaises(s2.ManagementError):
|
|
1428
|
+
space.exists(f'{mkdir_test_1_dir}/')
|
|
1429
|
+
|
|
1430
|
+
out = space.listdir('/')
|
|
1431
|
+
assert f'{mkdir_test_1_dir}/' not in out
|
|
1432
|
+
|
|
1433
|
+
with self.assertRaises(s2.ManagementError):
|
|
1434
|
+
space.rmdir(f'{mkdir_test_1_dir}/')
|
|
1435
|
+
|
|
1436
|
+
def test_os_rename(self):
|
|
1437
|
+
rename_test_ipynb = f'rename_test_{id(self)}.ipynb'
|
|
1438
|
+
rename_test_2_ipynb = f'rename_test_2_{id(self)}.ipynb'
|
|
1439
|
+
rename_test_3_ipynb = f'rename_test_3_{id(self)}.ipynb'
|
|
1440
|
+
|
|
1441
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1442
|
+
space.upload_file(
|
|
1443
|
+
TEST_DIR / 'test.ipynb',
|
|
1444
|
+
rename_test_ipynb,
|
|
1445
|
+
)
|
|
1446
|
+
assert rename_test_ipynb in space.listdir('/')
|
|
1447
|
+
assert rename_test_2_ipynb not in space.listdir('/')
|
|
1448
|
+
|
|
1449
|
+
space.rename(
|
|
1450
|
+
rename_test_ipynb,
|
|
1451
|
+
rename_test_2_ipynb,
|
|
1452
|
+
)
|
|
1453
|
+
assert rename_test_ipynb not in space.listdir('/')
|
|
1454
|
+
assert rename_test_2_ipynb in space.listdir('/')
|
|
1455
|
+
|
|
1456
|
+
# non-existent file
|
|
1457
|
+
with self.assertRaises(OSError):
|
|
1458
|
+
space.rename('rename_foo.ipynb', 'rename_foo_2.ipynb')
|
|
1459
|
+
|
|
1460
|
+
space.upload_file(
|
|
1461
|
+
TEST_DIR / 'test.ipynb',
|
|
1462
|
+
rename_test_3_ipynb,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1465
|
+
# overwrite
|
|
1466
|
+
with self.assertRaises(OSError):
|
|
1467
|
+
space.rename(
|
|
1468
|
+
rename_test_2_ipynb,
|
|
1469
|
+
rename_test_3_ipynb,
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
space.rename(
|
|
1473
|
+
rename_test_2_ipynb,
|
|
1474
|
+
rename_test_3_ipynb, overwrite=True,
|
|
1475
|
+
)
|
|
1476
|
+
|
|
1477
|
+
# Cleanup
|
|
1478
|
+
space.remove(rename_test_3_ipynb)
|
|
1479
|
+
|
|
1480
|
+
def test_file_object(self):
|
|
1481
|
+
obj_test_ipynb = f'obj_test_{id(self)}.ipynb'
|
|
1482
|
+
obj_test_2_ipynb = f'obj_test_2_{id(self)}.ipynb'
|
|
1483
|
+
|
|
1484
|
+
for space in [self.personal_space, self.shared_space]:
|
|
1485
|
+
f = space.upload_file(
|
|
1486
|
+
TEST_DIR / 'test.ipynb',
|
|
1487
|
+
obj_test_ipynb,
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
assert not f.is_dir()
|
|
1491
|
+
assert f.is_file()
|
|
1492
|
+
|
|
1493
|
+
# abspath / basename / dirname / exists
|
|
1494
|
+
assert f.abspath() == obj_test_ipynb
|
|
1495
|
+
assert f.basename() == obj_test_ipynb
|
|
1496
|
+
assert f.dirname() == '/'
|
|
1497
|
+
assert f.exists()
|
|
1498
|
+
|
|
1499
|
+
# download
|
|
1500
|
+
assert f.download(encoding='utf-8') == \
|
|
1501
|
+
open(TEST_DIR / 'test.ipynb', 'r').read()
|
|
1502
|
+
assert f.download() == open(TEST_DIR / 'test.ipynb', 'rb').read()
|
|
1503
|
+
|
|
1504
|
+
assert space.is_file(obj_test_ipynb)
|
|
1505
|
+
f.remove()
|
|
1506
|
+
assert not space.is_file(obj_test_ipynb)
|
|
1507
|
+
|
|
1508
|
+
# mtime / ctime
|
|
1509
|
+
assert f.getmtime() > 0
|
|
1510
|
+
assert f.getctime() > 0
|
|
1511
|
+
|
|
1512
|
+
# rename
|
|
1513
|
+
f = space.upload_file(
|
|
1514
|
+
TEST_DIR / 'test.ipynb',
|
|
1515
|
+
obj_test_ipynb,
|
|
1516
|
+
)
|
|
1517
|
+
assert space.exists(obj_test_ipynb)
|
|
1518
|
+
assert not space.exists(obj_test_2_ipynb)
|
|
1519
|
+
f.rename(obj_test_2_ipynb)
|
|
1520
|
+
assert not space.exists(obj_test_ipynb)
|
|
1521
|
+
assert space.exists(obj_test_2_ipynb)
|
|
1522
|
+
assert f.abspath() == obj_test_2_ipynb
|
|
1523
|
+
|
|
1524
|
+
# Cleanup
|
|
1525
|
+
space.remove(obj_test_2_ipynb)
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
@pytest.mark.management
|
|
1529
|
+
class TestRegions(unittest.TestCase):
|
|
1530
|
+
"""Test cases for region management."""
|
|
1531
|
+
|
|
1532
|
+
manager = None
|
|
1533
|
+
|
|
1534
|
+
@classmethod
|
|
1535
|
+
def setUpClass(cls):
|
|
1536
|
+
"""Set up the test environment."""
|
|
1537
|
+
cls.manager = s2.manage_regions()
|
|
1538
|
+
|
|
1539
|
+
@classmethod
|
|
1540
|
+
def tearDownClass(cls):
|
|
1541
|
+
"""Clean up the test environment."""
|
|
1542
|
+
cls.manager = None
|
|
1543
|
+
|
|
1544
|
+
def test_list_regions(self):
|
|
1545
|
+
"""Test listing all regions."""
|
|
1546
|
+
regions = self.manager.list_regions()
|
|
1547
|
+
|
|
1548
|
+
# Verify we get a NamedList
|
|
1549
|
+
assert isinstance(regions, NamedList)
|
|
1550
|
+
|
|
1551
|
+
# Verify we have at least one region
|
|
1552
|
+
assert len(regions) > 0
|
|
1553
|
+
|
|
1554
|
+
# Verify region properties
|
|
1555
|
+
region = regions[0]
|
|
1556
|
+
assert isinstance(region, Region)
|
|
1557
|
+
assert hasattr(region, 'id')
|
|
1558
|
+
assert hasattr(region, 'name')
|
|
1559
|
+
assert hasattr(region, 'provider')
|
|
1560
|
+
|
|
1561
|
+
# Verify provider values
|
|
1562
|
+
providers = {x.provider for x in regions}
|
|
1563
|
+
assert 'Azure' in providers or 'GCP' in providers or 'AWS' in providers
|
|
1564
|
+
|
|
1565
|
+
def test_list_shared_tier_regions(self):
|
|
1566
|
+
"""Test listing shared tier regions."""
|
|
1567
|
+
regions = self.manager.list_shared_tier_regions()
|
|
1568
|
+
|
|
1569
|
+
# Verify we get a NamedList
|
|
1570
|
+
assert isinstance(regions, NamedList)
|
|
1571
|
+
|
|
1572
|
+
# Verify region properties if we have any shared tier regions
|
|
1573
|
+
if regions:
|
|
1574
|
+
region = regions[0]
|
|
1575
|
+
assert isinstance(region, Region)
|
|
1576
|
+
assert hasattr(region, 'name')
|
|
1577
|
+
assert hasattr(region, 'provider')
|
|
1578
|
+
assert hasattr(region, 'region_name')
|
|
1579
|
+
|
|
1580
|
+
# Verify provider values
|
|
1581
|
+
providers = {x.provider for x in regions}
|
|
1582
|
+
assert any(p in providers for p in ['Azure', 'GCP', 'AWS'])
|
|
1583
|
+
|
|
1584
|
+
def test_str_repr(self):
|
|
1585
|
+
"""Test string representation of regions."""
|
|
1586
|
+
regions = self.manager.list_regions()
|
|
1587
|
+
if not regions:
|
|
1588
|
+
self.skipTest('No regions available for testing')
|
|
1589
|
+
|
|
1590
|
+
region = regions[0]
|
|
1591
|
+
|
|
1592
|
+
# Test __str__
|
|
1593
|
+
s = str(region)
|
|
1594
|
+
assert region.id in s
|
|
1595
|
+
assert region.name in s
|
|
1596
|
+
assert region.provider in s
|
|
1597
|
+
|
|
1598
|
+
# Test __repr__
|
|
1599
|
+
assert repr(region) == str(region)
|