singlestoredb 1.15.7__cp38-abi3-win32.whl → 1.16.0__cp38-abi3-win32.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 CHANGED
Binary file
singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.15.7'
16
+ __version__ = '1.16.0'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -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
- if http_client is not None and http_client.timeout is not None:
94
- cfg_kwargs['read_timeout'] = http_client.timeout
95
- cfg_kwargs['connect_timeout'] = http_client.timeout
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(
@@ -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
- if http_client is not None and http_client.timeout is not None:
70
- cfg_kwargs['read_timeout'] = http_client.timeout
71
- cfg_kwargs['connect_timeout'] = http_client.timeout
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(
@@ -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
@@ -1,5 +1,5 @@
1
+ from collections.abc import Iterable
1
2
  from typing import Any
2
- from typing import Iterable
3
3
  from typing import Tuple
4
4
  from typing import TypeVar
5
5
 
@@ -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
 
@@ -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
@@ -2,8 +2,8 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import re
5
+ from collections.abc import Iterable
5
6
  from typing import Any
6
- from typing import Iterable
7
7
  from typing import List
8
8
  from typing import Optional
9
9
  from typing import Tuple
@@ -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:
@@ -13,9 +13,9 @@ import struct
13
13
  import sys
14
14
  import traceback
15
15
  import warnings
16
+ from collections.abc import Iterable
16
17
  from typing import Any
17
18
  from typing import Dict
18
- from typing import Iterable
19
19
 
20
20
  try:
21
21
  import _singlestoredb_accel
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
- self.container_name = 'singlestoredb-test-container'
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
- self.ports = ['3306', '8080', '9000']
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
- self.url = f'root:{self.root_password}@127.0.0.1:3306'
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(f'Starting container {self.container_name}')
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
- 'Is one already running?',
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 port in self.ports:
195
+ for external_port, internal_port in self.ports:
127
196
  yield '-p'
128
- yield f'{port}:{port}'
197
+ yield f'{external_port}:{internal_port}'
129
198
 
130
199
  yield self.dev_image_name
131
200