singlestoredb 1.15.7__cp38-abi3-win_amd64.whl → 1.16.0__cp38-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- _singlestoredb_accel.pyd +0 -0
- singlestoredb/__init__.py +1 -1
- singlestoredb/ai/__init__.py +0 -3
- singlestoredb/ai/chat.py +21 -41
- singlestoredb/ai/embeddings.py +21 -18
- singlestoredb/connection.py +2 -2
- singlestoredb/functions/ext/asgi.py +3 -3
- singlestoredb/functions/ext/rowdat_1.py +1 -1
- singlestoredb/functions/signature.py +1 -1
- singlestoredb/functions/typing/__init__.py +1 -1
- singlestoredb/functions/utils.py +1 -1
- singlestoredb/fusion/handler.py +1 -1
- singlestoredb/fusion/result.py +1 -1
- singlestoredb/http/connection.py +2 -2
- singlestoredb/management/manager.py +61 -0
- singlestoredb/management/utils.py +1 -1
- singlestoredb/management/workspace.py +6 -0
- singlestoredb/mysql/connection.py +1 -1
- singlestoredb/pytest.py +78 -9
- singlestoredb/tests/test_management.py +151 -124
- singlestoredb/utils/config.py +2 -2
- singlestoredb/utils/mogrify.py +1 -1
- singlestoredb/utils/results.py +2 -2
- singlestoredb/utils/xdict.py +4 -4
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info}/METADATA +42 -22
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info}/RECORD +30 -30
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info}/WHEEL +1 -1
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info}/top_level.txt +2 -0
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info}/entry_points.txt +0 -0
- {singlestoredb-1.15.7.dist-info → singlestoredb-1.16.0.dist-info/licenses}/LICENSE +0 -0
_singlestoredb_accel.pyd
CHANGED
|
Binary file
|
singlestoredb/__init__.py
CHANGED
singlestoredb/ai/__init__.py
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
from .chat import SingleStoreChat # noqa: F401
|
|
2
1
|
from .chat import SingleStoreChatFactory # noqa: F401
|
|
3
|
-
from .chat import SingleStoreChatOpenAI # noqa: F401
|
|
4
|
-
from .embeddings import SingleStoreEmbeddings # noqa: F401
|
|
5
2
|
from .embeddings import SingleStoreEmbeddingsFactory # noqa: F401
|
singlestoredb/ai/chat.py
CHANGED
|
@@ -29,44 +29,6 @@ from botocore import UNSIGNED
|
|
|
29
29
|
from botocore.config import Config
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class SingleStoreChatOpenAI(ChatOpenAI):
|
|
33
|
-
def __init__(self, model_name: str, api_key: Optional[str] = None, **kwargs: Any):
|
|
34
|
-
inference_api_manger = (
|
|
35
|
-
manage_workspaces().organizations.current.inference_apis
|
|
36
|
-
)
|
|
37
|
-
info = inference_api_manger.get(model_name=model_name)
|
|
38
|
-
token = (
|
|
39
|
-
api_key
|
|
40
|
-
if api_key is not None
|
|
41
|
-
else os.environ.get('SINGLESTOREDB_USER_TOKEN')
|
|
42
|
-
)
|
|
43
|
-
super().__init__(
|
|
44
|
-
base_url=info.connection_url,
|
|
45
|
-
api_key=token,
|
|
46
|
-
model=model_name,
|
|
47
|
-
**kwargs,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class SingleStoreChat(ChatOpenAI):
|
|
52
|
-
def __init__(self, model_name: str, api_key: Optional[str] = None, **kwargs: Any):
|
|
53
|
-
inference_api_manger = (
|
|
54
|
-
manage_workspaces().organizations.current.inference_apis
|
|
55
|
-
)
|
|
56
|
-
info = inference_api_manger.get(model_name=model_name)
|
|
57
|
-
token = (
|
|
58
|
-
api_key
|
|
59
|
-
if api_key is not None
|
|
60
|
-
else os.environ.get('SINGLESTOREDB_USER_TOKEN')
|
|
61
|
-
)
|
|
62
|
-
super().__init__(
|
|
63
|
-
base_url=info.connection_url,
|
|
64
|
-
api_key=token,
|
|
65
|
-
model=model_name,
|
|
66
|
-
**kwargs,
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
32
|
def SingleStoreChatFactory(
|
|
71
33
|
model_name: str,
|
|
72
34
|
api_key: Optional[str] = None,
|
|
@@ -90,9 +52,27 @@ def SingleStoreChatFactory(
|
|
|
90
52
|
'signature_version': UNSIGNED,
|
|
91
53
|
'retries': {'max_attempts': 1, 'mode': 'standard'},
|
|
92
54
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
55
|
+
# Extract timeouts from http_client if provided
|
|
56
|
+
t = http_client.timeout if http_client is not None else None
|
|
57
|
+
connect_timeout = None
|
|
58
|
+
read_timeout = None
|
|
59
|
+
if t is not None:
|
|
60
|
+
if isinstance(t, httpx.Timeout):
|
|
61
|
+
if t.connect is not None:
|
|
62
|
+
connect_timeout = float(t.connect)
|
|
63
|
+
if t.read is not None:
|
|
64
|
+
read_timeout = float(t.read)
|
|
65
|
+
if connect_timeout is None and read_timeout is not None:
|
|
66
|
+
connect_timeout = read_timeout
|
|
67
|
+
if read_timeout is None and connect_timeout is not None:
|
|
68
|
+
read_timeout = connect_timeout
|
|
69
|
+
elif isinstance(t, (int, float)):
|
|
70
|
+
connect_timeout = float(t)
|
|
71
|
+
read_timeout = float(t)
|
|
72
|
+
if read_timeout is not None:
|
|
73
|
+
cfg_kwargs['read_timeout'] = read_timeout
|
|
74
|
+
if connect_timeout is not None:
|
|
75
|
+
cfg_kwargs['connect_timeout'] = connect_timeout
|
|
96
76
|
|
|
97
77
|
cfg = Config(**cfg_kwargs)
|
|
98
78
|
client = boto3.client(
|
singlestoredb/ai/embeddings.py
CHANGED
|
@@ -29,21 +29,6 @@ from botocore import UNSIGNED
|
|
|
29
29
|
from botocore.config import Config
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class SingleStoreEmbeddings(OpenAIEmbeddings):
|
|
33
|
-
|
|
34
|
-
def __init__(self, model_name: str, **kwargs: Any):
|
|
35
|
-
inference_api_manger = (
|
|
36
|
-
manage_workspaces().organizations.current.inference_apis
|
|
37
|
-
)
|
|
38
|
-
info = inference_api_manger.get(model_name=model_name)
|
|
39
|
-
super().__init__(
|
|
40
|
-
base_url=info.connection_url,
|
|
41
|
-
api_key=os.environ.get('SINGLESTOREDB_USER_TOKEN'),
|
|
42
|
-
model=model_name,
|
|
43
|
-
**kwargs,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
|
|
47
32
|
def SingleStoreEmbeddingsFactory(
|
|
48
33
|
model_name: str,
|
|
49
34
|
api_key: Optional[str] = None,
|
|
@@ -66,9 +51,27 @@ def SingleStoreEmbeddingsFactory(
|
|
|
66
51
|
'signature_version': UNSIGNED,
|
|
67
52
|
'retries': {'max_attempts': 1, 'mode': 'standard'},
|
|
68
53
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
54
|
+
# Extract timeouts from http_client if provided
|
|
55
|
+
t = http_client.timeout if http_client is not None else None
|
|
56
|
+
connect_timeout = None
|
|
57
|
+
read_timeout = None
|
|
58
|
+
if t is not None:
|
|
59
|
+
if isinstance(t, httpx.Timeout):
|
|
60
|
+
if t.connect is not None:
|
|
61
|
+
connect_timeout = float(t.connect)
|
|
62
|
+
if t.read is not None:
|
|
63
|
+
read_timeout = float(t.read)
|
|
64
|
+
if connect_timeout is None and read_timeout is not None:
|
|
65
|
+
connect_timeout = read_timeout
|
|
66
|
+
if read_timeout is None and connect_timeout is not None:
|
|
67
|
+
read_timeout = connect_timeout
|
|
68
|
+
elif isinstance(t, (int, float)):
|
|
69
|
+
connect_timeout = float(t)
|
|
70
|
+
read_timeout = float(t)
|
|
71
|
+
if read_timeout is not None:
|
|
72
|
+
cfg_kwargs['read_timeout'] = read_timeout
|
|
73
|
+
if connect_timeout is not None:
|
|
74
|
+
cfg_kwargs['connect_timeout'] = connect_timeout
|
|
72
75
|
|
|
73
76
|
cfg = Config(**cfg_kwargs)
|
|
74
77
|
client = boto3.client(
|
singlestoredb/connection.py
CHANGED
|
@@ -9,15 +9,15 @@ import re
|
|
|
9
9
|
import sys
|
|
10
10
|
import warnings
|
|
11
11
|
import weakref
|
|
12
|
+
from collections.abc import Iterator
|
|
12
13
|
from collections.abc import Mapping
|
|
13
14
|
from collections.abc import MutableMapping
|
|
15
|
+
from collections.abc import Sequence
|
|
14
16
|
from typing import Any
|
|
15
17
|
from typing import Callable
|
|
16
18
|
from typing import Dict
|
|
17
|
-
from typing import Iterator
|
|
18
19
|
from typing import List
|
|
19
20
|
from typing import Optional
|
|
20
|
-
from typing import Sequence
|
|
21
21
|
from typing import Tuple
|
|
22
22
|
from typing import Union
|
|
23
23
|
from urllib.parse import parse_qs
|
|
@@ -48,15 +48,15 @@ import urllib
|
|
|
48
48
|
import uuid
|
|
49
49
|
import zipfile
|
|
50
50
|
import zipimport
|
|
51
|
+
from collections.abc import Awaitable
|
|
52
|
+
from collections.abc import Iterable
|
|
53
|
+
from collections.abc import Sequence
|
|
51
54
|
from types import ModuleType
|
|
52
55
|
from typing import Any
|
|
53
|
-
from typing import Awaitable
|
|
54
56
|
from typing import Callable
|
|
55
57
|
from typing import Dict
|
|
56
|
-
from typing import Iterable
|
|
57
58
|
from typing import List
|
|
58
59
|
from typing import Optional
|
|
59
|
-
from typing import Sequence
|
|
60
60
|
from typing import Set
|
|
61
61
|
from typing import Tuple
|
|
62
62
|
from typing import Union
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import struct
|
|
3
3
|
import warnings
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
from io import BytesIO
|
|
5
6
|
from typing import Any
|
|
6
7
|
from typing import List
|
|
7
8
|
from typing import Optional
|
|
8
|
-
from typing import Sequence
|
|
9
9
|
from typing import Tuple
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
@@ -9,12 +9,12 @@ import string
|
|
|
9
9
|
import sys
|
|
10
10
|
import types
|
|
11
11
|
import typing
|
|
12
|
+
from collections.abc import Sequence
|
|
12
13
|
from typing import Any
|
|
13
14
|
from typing import Callable
|
|
14
15
|
from typing import Dict
|
|
15
16
|
from typing import List
|
|
16
17
|
from typing import Optional
|
|
17
|
-
from typing import Sequence
|
|
18
18
|
from typing import Tuple
|
|
19
19
|
from typing import TypeVar
|
|
20
20
|
from typing import Union
|
singlestoredb/functions/utils.py
CHANGED
|
@@ -4,10 +4,10 @@ import struct
|
|
|
4
4
|
import sys
|
|
5
5
|
import types
|
|
6
6
|
import typing
|
|
7
|
+
from collections.abc import Iterable
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from typing import Any
|
|
9
10
|
from typing import Dict
|
|
10
|
-
from typing import Iterable
|
|
11
11
|
from typing import Tuple
|
|
12
12
|
from typing import Union
|
|
13
13
|
|
singlestoredb/fusion/handler.py
CHANGED
|
@@ -5,10 +5,10 @@ import os
|
|
|
5
5
|
import re
|
|
6
6
|
import sys
|
|
7
7
|
import textwrap
|
|
8
|
+
from collections.abc import Iterable
|
|
8
9
|
from typing import Any
|
|
9
10
|
from typing import Callable
|
|
10
11
|
from typing import Dict
|
|
11
|
-
from typing import Iterable
|
|
12
12
|
from typing import List
|
|
13
13
|
from typing import Optional
|
|
14
14
|
from typing import Set
|
singlestoredb/fusion/result.py
CHANGED
singlestoredb/http/connection.py
CHANGED
|
@@ -10,13 +10,13 @@ import os
|
|
|
10
10
|
import re
|
|
11
11
|
import time
|
|
12
12
|
from base64 import b64decode
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from collections.abc import Sequence
|
|
13
15
|
from typing import Any
|
|
14
16
|
from typing import Callable
|
|
15
17
|
from typing import Dict
|
|
16
|
-
from typing import Iterable
|
|
17
18
|
from typing import List
|
|
18
19
|
from typing import Optional
|
|
19
|
-
from typing import Sequence
|
|
20
20
|
from typing import Tuple
|
|
21
21
|
from typing import Union
|
|
22
22
|
from urllib.parse import urljoin
|
|
@@ -15,6 +15,7 @@ import requests
|
|
|
15
15
|
|
|
16
16
|
from .. import config
|
|
17
17
|
from ..exceptions import ManagementError
|
|
18
|
+
from ..exceptions import OperationalError
|
|
18
19
|
from .utils import get_token
|
|
19
20
|
|
|
20
21
|
|
|
@@ -310,3 +311,63 @@ class Manager(object):
|
|
|
310
311
|
out = getattr(self, f'get_{self.obj_type}')(out.id)
|
|
311
312
|
|
|
312
313
|
return out
|
|
314
|
+
|
|
315
|
+
def _wait_on_endpoint(
|
|
316
|
+
self,
|
|
317
|
+
out: Any,
|
|
318
|
+
interval: int = 10,
|
|
319
|
+
timeout: int = 300,
|
|
320
|
+
) -> Any:
|
|
321
|
+
"""
|
|
322
|
+
Wait for the endpoint to be ready by attempting to connect.
|
|
323
|
+
|
|
324
|
+
Parameters
|
|
325
|
+
----------
|
|
326
|
+
out : Any
|
|
327
|
+
Workspace object with a connect method
|
|
328
|
+
interval : int, optional
|
|
329
|
+
Interval between each connection attempt (default: 10 seconds)
|
|
330
|
+
timeout : int, optional
|
|
331
|
+
Maximum time to wait before raising an exception (default: 300 seconds)
|
|
332
|
+
|
|
333
|
+
Raises
|
|
334
|
+
------
|
|
335
|
+
ManagementError
|
|
336
|
+
If timeout is reached or endpoint is not available
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
Same object type as `out`
|
|
341
|
+
|
|
342
|
+
"""
|
|
343
|
+
# Only wait if workload type is set which means we are in the
|
|
344
|
+
# notebook environment. Outside of the environment, the endpoint
|
|
345
|
+
# may not be reachable directly.
|
|
346
|
+
if not os.environ.get('SINGLESTOREDB_WORKLOAD_TYPE', ''):
|
|
347
|
+
return out
|
|
348
|
+
|
|
349
|
+
if not hasattr(out, 'connect') or not out.connect:
|
|
350
|
+
raise ManagementError(
|
|
351
|
+
msg=f'{type(out).__name__} object does not have a valid endpoint',
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
while True:
|
|
355
|
+
try:
|
|
356
|
+
# Try to establish a connection to the endpoint using context manager
|
|
357
|
+
with out.connect(connect_timeout=5):
|
|
358
|
+
pass
|
|
359
|
+
except Exception as exc:
|
|
360
|
+
# If we get an 'access denied' error, that means that the server is
|
|
361
|
+
# up and we just aren't authenticating.
|
|
362
|
+
if isinstance(exc, OperationalError) and exc.errno == 1045:
|
|
363
|
+
break
|
|
364
|
+
# If connection fails, check timeout and retry
|
|
365
|
+
if timeout <= 0:
|
|
366
|
+
raise ManagementError(
|
|
367
|
+
msg=f'Exceeded waiting time for {self.obj_type} endpoint '
|
|
368
|
+
'to become ready',
|
|
369
|
+
)
|
|
370
|
+
time.sleep(interval)
|
|
371
|
+
timeout -= interval
|
|
372
|
+
|
|
373
|
+
return out
|
|
@@ -6,11 +6,11 @@ import itertools
|
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
|
+
from collections.abc import Mapping
|
|
9
10
|
from typing import Any
|
|
10
11
|
from typing import Callable
|
|
11
12
|
from typing import Dict
|
|
12
13
|
from typing import List
|
|
13
|
-
from typing import Mapping
|
|
14
14
|
from typing import Optional
|
|
15
15
|
from typing import SupportsIndex
|
|
16
16
|
from typing import Tuple
|
|
@@ -1794,6 +1794,12 @@ class WorkspaceManager(Manager):
|
|
|
1794
1794
|
interval=wait_interval,
|
|
1795
1795
|
timeout=wait_timeout,
|
|
1796
1796
|
)
|
|
1797
|
+
# After workspace is active, wait for endpoint to be ready
|
|
1798
|
+
out = self._wait_on_endpoint(
|
|
1799
|
+
out,
|
|
1800
|
+
interval=wait_interval,
|
|
1801
|
+
timeout=wait_timeout,
|
|
1802
|
+
)
|
|
1797
1803
|
return out
|
|
1798
1804
|
|
|
1799
1805
|
def get_workspace_group(self, id: str) -> WorkspaceGroup:
|
singlestoredb/pytest.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
"""Pytest plugin"""
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import socket
|
|
5
6
|
import subprocess
|
|
6
7
|
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from collections.abc import Iterator
|
|
7
10
|
from enum import Enum
|
|
8
|
-
from typing import Iterator
|
|
9
11
|
from typing import Optional
|
|
10
12
|
|
|
11
13
|
import pytest
|
|
@@ -28,6 +30,14 @@ TEARDOWN_WAIT_ATTEMPTS = 20
|
|
|
28
30
|
TEARDOWN_WAIT_SECONDS = 2
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
def _find_free_port() -> int:
|
|
34
|
+
"""Find a free port by binding to port 0 and getting the assigned port."""
|
|
35
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
36
|
+
s.bind(('', 0))
|
|
37
|
+
s.listen(1)
|
|
38
|
+
return s.getsockname()[1]
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
class ExecutionMode(Enum):
|
|
32
42
|
SEQUENTIAL = 1
|
|
33
43
|
LEADER = 2
|
|
@@ -79,7 +89,11 @@ class _TestContainerManager():
|
|
|
79
89
|
"""Manages the setup and teardown of a SingleStoreDB Dev Container"""
|
|
80
90
|
|
|
81
91
|
def __init__(self) -> None:
|
|
82
|
-
|
|
92
|
+
# Generate unique container name using UUID and worker ID
|
|
93
|
+
worker = os.environ.get('PYTEST_XDIST_WORKER', 'master')
|
|
94
|
+
unique_id = uuid.uuid4().hex[:8]
|
|
95
|
+
self.container_name = f'singlestoredb-test-{worker}-{unique_id}'
|
|
96
|
+
|
|
83
97
|
self.dev_image_name = 'ghcr.io/singlestore-labs/singlestoredb-dev'
|
|
84
98
|
|
|
85
99
|
assert 'SINGLESTORE_LICENSE' in os.environ, 'SINGLESTORE_LICENSE not set'
|
|
@@ -91,14 +105,69 @@ class _TestContainerManager():
|
|
|
91
105
|
'SINGLESTORE_SET_GLOBAL_DEFAULT_PARTITIONS_PER_LEAF': '1',
|
|
92
106
|
}
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
# Use dynamic port allocation to avoid conflicts
|
|
109
|
+
self.mysql_port = _find_free_port()
|
|
110
|
+
self.http_port = _find_free_port()
|
|
111
|
+
self.studio_port = _find_free_port()
|
|
112
|
+
self.ports = [
|
|
113
|
+
(self.mysql_port, '3306'), # External port -> Internal port
|
|
114
|
+
(self.http_port, '8080'),
|
|
115
|
+
(self.studio_port, '9000'),
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
self.url = f'root:{self.root_password}@127.0.0.1:{self.mysql_port}'
|
|
119
|
+
|
|
120
|
+
def _container_exists(self) -> bool:
|
|
121
|
+
"""Check if a container with this name already exists."""
|
|
122
|
+
try:
|
|
123
|
+
result = subprocess.run(
|
|
124
|
+
[
|
|
125
|
+
'docker', 'ps', '-a', '--filter',
|
|
126
|
+
f'name={self.container_name}',
|
|
127
|
+
'--format', '{{.Names}}',
|
|
128
|
+
],
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
check=True,
|
|
132
|
+
)
|
|
133
|
+
return self.container_name in result.stdout
|
|
134
|
+
except subprocess.CalledProcessError:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def _cleanup_existing_container(self) -> None:
|
|
138
|
+
"""Stop and remove any existing container with the same name."""
|
|
139
|
+
if not self._container_exists():
|
|
140
|
+
return
|
|
95
141
|
|
|
96
|
-
|
|
142
|
+
logger.info(f'Found existing container {self.container_name}, cleaning up')
|
|
143
|
+
try:
|
|
144
|
+
# Try to stop the container (ignore if it's already stopped)
|
|
145
|
+
subprocess.run(
|
|
146
|
+
['docker', 'stop', self.container_name],
|
|
147
|
+
capture_output=True,
|
|
148
|
+
check=False,
|
|
149
|
+
)
|
|
150
|
+
# Remove the container
|
|
151
|
+
subprocess.run(
|
|
152
|
+
['docker', 'rm', self.container_name],
|
|
153
|
+
capture_output=True,
|
|
154
|
+
check=True,
|
|
155
|
+
)
|
|
156
|
+
logger.debug(f'Cleaned up existing container {self.container_name}')
|
|
157
|
+
except subprocess.CalledProcessError as e:
|
|
158
|
+
logger.warning(f'Failed to cleanup existing container: {e}')
|
|
159
|
+
# Continue anyway - the unique name should prevent most conflicts
|
|
97
160
|
|
|
98
161
|
def start(self) -> None:
|
|
162
|
+
# Clean up any existing container with the same name
|
|
163
|
+
self._cleanup_existing_container()
|
|
164
|
+
|
|
99
165
|
command = ' '.join(self._start_command())
|
|
100
166
|
|
|
101
|
-
logger.info(
|
|
167
|
+
logger.info(
|
|
168
|
+
f'Starting container {self.container_name} on ports {self.mysql_port}, '
|
|
169
|
+
f'{self.http_port}, {self.studio_port}',
|
|
170
|
+
)
|
|
102
171
|
try:
|
|
103
172
|
license = os.environ['SINGLESTORE_LICENSE']
|
|
104
173
|
env = {
|
|
@@ -108,8 +177,8 @@ class _TestContainerManager():
|
|
|
108
177
|
except Exception as e:
|
|
109
178
|
logger.exception(e)
|
|
110
179
|
raise RuntimeError(
|
|
111
|
-
'Failed to start container. '
|
|
112
|
-
'
|
|
180
|
+
f'Failed to start container {self.container_name}. '
|
|
181
|
+
f'Command: {command}',
|
|
113
182
|
) from e
|
|
114
183
|
logger.debug('Container started')
|
|
115
184
|
|
|
@@ -123,9 +192,9 @@ class _TestContainerManager():
|
|
|
123
192
|
else:
|
|
124
193
|
yield f'{key}={value}'
|
|
125
194
|
|
|
126
|
-
for
|
|
195
|
+
for external_port, internal_port in self.ports:
|
|
127
196
|
yield '-p'
|
|
128
|
-
yield f'{
|
|
197
|
+
yield f'{external_port}:{internal_port}'
|
|
129
198
|
|
|
130
199
|
yield self.dev_image_name
|
|
131
200
|
|