singlestoredb 1.3.1__cp38-abi3-macosx_10_9_universal2.whl → 1.4.1__cp38-abi3-macosx_10_9_universal2.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.

Binary file
singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.3.1'
16
+ __version__ = '1.4.1'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -0,0 +1 @@
1
+ from .embeddings import SingleStoreEmbeddings # noqa: F401
@@ -0,0 +1,24 @@
1
+ import os as _os
2
+ from typing import Any
3
+
4
+ try:
5
+ from langchain_community.embeddings.ollama import OllamaEmbeddings
6
+ except ImportError:
7
+ raise ImportError(
8
+ 'Could not import langchain_community python package. '
9
+ 'Please install it with `pip install langchain_community`.',
10
+ )
11
+
12
+
13
+ class SingleStoreEmbeddings(OllamaEmbeddings):
14
+
15
+ def __init__(self, **kwargs: Any):
16
+ url = _os.getenv('SINGLESTORE_AI_EXPERIMENTAL_URL')
17
+ if not url:
18
+ raise ValueError(
19
+ "Environment variable 'SINGLESTORE_AI_EXPERIMENTAL_URL' must be set",
20
+ )
21
+
22
+ base_url = url.strip('/v1')
23
+ kwargs = {'model': 'nomic-embed-text', **kwargs}
24
+ super().__init__(base_url=base_url, **kwargs)
@@ -1359,6 +1359,10 @@ def connect(
1359
1359
  results_type : str, optional
1360
1360
  The form of the query results: tuples, namedtuples, dicts,
1361
1361
  numpy, polars, pandas, arrow
1362
+ buffered : bool, optional
1363
+ Should the entire query result be buffered in memory? This is the default
1364
+ behavior which allows full cursor control of the result, but does consume
1365
+ more memory.
1362
1366
  results_format : str, optional
1363
1367
  Deprecated. This option has been renamed to results_type.
1364
1368
  program_name : str, optional
@@ -45,15 +45,17 @@ if has_numpy:
45
45
  np.uint8: 'uint8',
46
46
  np.longlong: 'uint64',
47
47
  np.ulonglong: 'uint64',
48
- np.unicode_: 'str',
49
48
  np.str_: 'str',
50
49
  np.bytes_: 'bytes',
51
- np.float_: 'float64',
52
50
  np.float64: 'float64',
53
51
  np.float32: 'float32',
54
52
  np.float16: 'float16',
55
53
  np.double: 'float64',
56
54
  }
55
+ if hasattr(np, 'unicode_'):
56
+ numpy_type_map[np.unicode_] = 'str'
57
+ if hasattr(np, 'float_'):
58
+ numpy_type_map[np.float_] = 'float64'
57
59
  else:
58
60
  array_types = (Sequence,)
59
61
  numpy_type_map = {}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  from .cluster import manage_cluster
3
- from .manager import get_organization
4
3
  from .manager import get_token
4
+ from .workspace import get_organization
5
5
  from .workspace import get_secret
6
6
  from .workspace import get_stage
7
7
  from .workspace import manage_workspaces
@@ -8,6 +8,7 @@ from typing import List
8
8
  from typing import Optional
9
9
  from typing import Union
10
10
 
11
+ from .. import config
11
12
  from .. import connection
12
13
  from ..exceptions import ManagementError
13
14
  from .manager import Manager
@@ -332,7 +333,7 @@ class ClusterManager(Manager):
332
333
  default_version = 'v0beta'
333
334
 
334
335
  #: Base URL if none is specified.
335
- default_base_url = 'https://api.singlestore.com'
336
+ default_base_url = config.get_option('management.base_url')
336
337
 
337
338
  #: Object type
338
339
  obj_type = 'cluster'
@@ -425,8 +426,8 @@ class ClusterManager(Manager):
425
426
 
426
427
  def manage_cluster(
427
428
  access_token: Optional[str] = None,
428
- version: str = ClusterManager.default_version,
429
- base_url: str = ClusterManager.default_base_url,
429
+ version: Optional[str] = None,
430
+ base_url: Optional[str] = None,
430
431
  *,
431
432
  organization_id: Optional[str] = None,
432
433
  ) -> ClusterManager:
@@ -15,7 +15,6 @@ import requests
15
15
 
16
16
  from .. import config
17
17
  from ..exceptions import ManagementError
18
- from .utils import get_organization
19
18
  from .utils import get_token
20
19
 
21
20
 
@@ -24,7 +23,7 @@ def set_organization(kwargs: Dict[str, Any]) -> None:
24
23
  if kwargs.get('params', {}).get('organizationID', None):
25
24
  return
26
25
 
27
- org = get_organization()
26
+ org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
28
27
  if org:
29
28
  if 'params' not in kwargs:
30
29
  kwargs['params'] = {}
@@ -2,6 +2,7 @@
2
2
  """SingleStoreDB Cluster Management."""
3
3
  import datetime
4
4
  import functools
5
+ import itertools
5
6
  import os
6
7
  import re
7
8
  import sys
@@ -12,6 +13,7 @@ from typing import List
12
13
  from typing import Mapping
13
14
  from typing import Optional
14
15
  from typing import SupportsIndex
16
+ from typing import Tuple
15
17
  from typing import TypeVar
16
18
  from typing import Union
17
19
  from urllib.parse import urlparse
@@ -20,6 +22,7 @@ import jwt
20
22
 
21
23
  from .. import converters
22
24
  from ..config import get_option
25
+ from ..utils import events
23
26
 
24
27
  JSON = Union[str, List[str], Dict[str, 'JSON']]
25
28
  JSONObj = Dict[str, JSON]
@@ -117,6 +120,66 @@ class NamedList(List[T]):
117
120
  raise
118
121
 
119
122
 
123
+ def _setup_authentication_info_handler() -> Callable[..., Dict[str, Any]]:
124
+ """Setup authentication info event handler."""
125
+
126
+ authentication_info: List[Tuple[str, Any]] = []
127
+
128
+ def handle_authentication_info(msg: Dict[str, Any]) -> None:
129
+ """Handle authentication info events."""
130
+ nonlocal authentication_info
131
+ if msg.get('name', '') != 'singlestore.portal.authentication_updated':
132
+ return
133
+ authentication_info = list(msg.get('data', {}).items())
134
+
135
+ events.subscribe(handle_authentication_info)
136
+
137
+ def handle_connection_info(msg: Dict[str, Any]) -> None:
138
+ """Handle connection info events."""
139
+ nonlocal authentication_info
140
+ if msg.get('name', '') != 'singlestore.portal.connection_updated':
141
+ return
142
+ data = msg.get('data', {})
143
+ out = {}
144
+ if 'user' in data:
145
+ out['user'] = data['user']
146
+ if 'password' in data:
147
+ out['password'] = data['password']
148
+ authentication_info = list(out.items())
149
+
150
+ events.subscribe(handle_authentication_info)
151
+
152
+ def get_env() -> List[Tuple[str, Any]]:
153
+ conn = {}
154
+ url = os.environ.get('SINGLESTOREDB_URL') or get_option('host')
155
+ if url:
156
+ urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
157
+ conn = dict(
158
+ user=urlp.username or None,
159
+ password=urlp.password or None,
160
+ )
161
+
162
+ return [
163
+ x for x in dict(
164
+ **conn,
165
+ ).items() if x[1] is not None
166
+ ]
167
+
168
+ def get_authentication_info(include_env: bool = True) -> Dict[str, Any]:
169
+ """Return authentication info from event."""
170
+ return dict(
171
+ itertools.chain(
172
+ (get_env() if include_env else []),
173
+ authentication_info,
174
+ ),
175
+ )
176
+
177
+ return get_authentication_info
178
+
179
+
180
+ get_authentication_info = _setup_authentication_info_handler()
181
+
182
+
120
183
  def get_token() -> Optional[str]:
121
184
  """Return the token for the Management API."""
122
185
  # See if an API key is configured
@@ -124,18 +187,11 @@ def get_token() -> Optional[str]:
124
187
  if tok:
125
188
  return tok
126
189
 
127
- url = os.environ.get('SINGLESTOREDB_URL')
128
- if not url:
129
- # See if the connection URL contains a JWT
130
- url = get_option('host')
131
- if not url:
132
- return None
133
-
134
- urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
135
- if urlp.password:
190
+ tok = get_authentication_info(include_env=True).get('password')
191
+ if tok:
136
192
  try:
137
- jwt.decode(urlp.password, options={'verify_signature': False})
138
- return urlp.password
193
+ jwt.decode(tok, options={'verify_signature': False})
194
+ return tok
139
195
  except jwt.DecodeError:
140
196
  pass
141
197
 
@@ -143,15 +199,6 @@ def get_token() -> Optional[str]:
143
199
  return None
144
200
 
145
201
 
146
- def get_organization() -> Optional[str]:
147
- """Return the organization for the current token or environment."""
148
- org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
149
- if org:
150
- return org
151
-
152
- return None
153
-
154
-
155
202
  def enable_http_tracing() -> None:
156
203
  """Enable tracing of HTTP requests."""
157
204
  import logging
@@ -269,7 +316,7 @@ def snake_to_camel(s: Optional[str], cap_first: bool = False) -> Optional[str]:
269
316
  """Convert snake-case to camel-case."""
270
317
  if s is None:
271
318
  return None
272
- out = re.sub(r'_[A-Za-z]', _upper_match, s.lower())
319
+ out = re.sub(r'_([A-Za-z])', _upper_match, s.lower())
273
320
  if cap_first and out:
274
321
  return out[0].upper() + out[1:]
275
322
  return out
@@ -17,6 +17,7 @@ from typing import Optional
17
17
  from typing import TextIO
18
18
  from typing import Union
19
19
 
20
+ from .. import config
20
21
  from .. import connection
21
22
  from ..exceptions import ManagementError
22
23
  from .billing_usage import BillingUsageItem
@@ -1690,10 +1691,10 @@ class WorkspaceManager(Manager):
1690
1691
  """
1691
1692
 
1692
1693
  #: Workspace management API version if none is specified.
1693
- default_version = 'v1'
1694
+ default_version = config.get_option('management.version')
1694
1695
 
1695
1696
  #: Base URL if none is specified.
1696
- default_base_url = 'https://api.singlestore.com'
1697
+ default_base_url = config.get_option('management.base_url')
1697
1698
 
1698
1699
  #: Object type
1699
1700
  obj_type = 'workspace'
@@ -1906,8 +1907,8 @@ class WorkspaceManager(Manager):
1906
1907
 
1907
1908
  def manage_workspaces(
1908
1909
  access_token: Optional[str] = None,
1909
- version: str = WorkspaceManager.default_version,
1910
- base_url: str = WorkspaceManager.default_base_url,
1910
+ version: Optional[str] = None,
1911
+ base_url: Optional[str] = None,
1911
1912
  *,
1912
1913
  organization_id: Optional[str] = None,
1913
1914
  ) -> WorkspaceManager:
@@ -13,6 +13,8 @@ import struct
13
13
  import sys
14
14
  import traceback
15
15
  import warnings
16
+ from typing import Any
17
+ from typing import Dict
16
18
  from typing import Iterable
17
19
 
18
20
  try:
@@ -21,6 +23,7 @@ except (ImportError, ModuleNotFoundError):
21
23
  _singlestoredb_accel = None
22
24
 
23
25
  from . import _auth
26
+ from ..utils import events
24
27
 
25
28
  from .charset import charset_by_name, charset_by_id
26
29
  from .constants import CLIENT, COMMAND, CR, ER, FIELD_TYPE, SERVER_STATUS
@@ -100,6 +103,19 @@ TEXT_TYPES = {
100
103
  FIELD_TYPE.VAR_STRING,
101
104
  FIELD_TYPE.VARCHAR,
102
105
  FIELD_TYPE.GEOMETRY,
106
+ FIELD_TYPE.BSON,
107
+ FIELD_TYPE.FLOAT32_VECTOR_JSON,
108
+ FIELD_TYPE.FLOAT64_VECTOR_JSON,
109
+ FIELD_TYPE.INT8_VECTOR_JSON,
110
+ FIELD_TYPE.INT16_VECTOR_JSON,
111
+ FIELD_TYPE.INT32_VECTOR_JSON,
112
+ FIELD_TYPE.INT64_VECTOR_JSON,
113
+ FIELD_TYPE.FLOAT32_VECTOR,
114
+ FIELD_TYPE.FLOAT64_VECTOR,
115
+ FIELD_TYPE.INT8_VECTOR,
116
+ FIELD_TYPE.INT16_VECTOR,
117
+ FIELD_TYPE.INT32_VECTOR,
118
+ FIELD_TYPE.INT64_VECTOR,
103
119
  }
104
120
 
105
121
  UNSET = 'unset'
@@ -614,15 +630,22 @@ class Connection(BaseConnection):
614
630
  if k not in self._connect_attrs:
615
631
  self._connect_attrs[k] = v
616
632
 
633
+ self._is_committable = True
617
634
  self._in_sync = False
618
635
  self._track_env = bool(track_env) or self.host == 'singlestore.com'
619
636
  self._enable_extended_data_types = enable_extended_data_types
637
+ self._connection_info = {}
638
+ events.subscribe(self._handle_event)
620
639
 
621
640
  if defer_connect or self._track_env:
622
641
  self._sock = None
623
642
  else:
624
643
  self.connect()
625
644
 
645
+ def _handle_event(self, data: Dict[str, Any]) -> None:
646
+ if data.get('name', '') == 'singlestore.portal.connection_updated':
647
+ self._connection_info = dict(data)
648
+
626
649
  @property
627
650
  def messages(self):
628
651
  # TODO
@@ -766,7 +789,8 @@ class Connection(BaseConnection):
766
789
 
767
790
  """
768
791
  log_query('COMMIT')
769
- if self.host == 'singlestore.com':
792
+ if not self._is_committable or self.host == 'singlestore.com':
793
+ self._is_committable = True
770
794
  return
771
795
  self._execute_command(COMMAND.COM_QUERY, 'COMMIT')
772
796
  self._read_ok_packet()
@@ -780,7 +804,8 @@ class Connection(BaseConnection):
780
804
 
781
805
  """
782
806
  log_query('ROLLBACK')
783
- if self.host == 'singlestore.com':
807
+ if not self._is_committable or self.host == 'singlestore.com':
808
+ self._is_committable = True
784
809
  return
785
810
  self._execute_command(COMMAND.COM_QUERY, 'ROLLBACK')
786
811
  self._read_ok_packet()
@@ -858,9 +883,11 @@ class Connection(BaseConnection):
858
883
  # print("DEBUG: sending query:", sql)
859
884
  handler = fusion.get_handler(sql)
860
885
  if handler is not None:
886
+ self._is_committable = False
861
887
  self._result = fusion.execute(self, sql, handler=handler)
862
888
  self._affected_rows = self._result.affected_rows
863
889
  else:
890
+ self._is_committable = True
864
891
  if isinstance(sql, str):
865
892
  sql = sql.encode(self.encoding, 'surrogateescape')
866
893
  self._local_infile_stream = infile_stream
@@ -973,9 +1000,11 @@ class Connection(BaseConnection):
973
1000
  if not self._track_env:
974
1001
  return
975
1002
 
976
- url = os.environ.get('SINGLESTOREDB_URL')
1003
+ url = self._connection_info.get('connection_url')
977
1004
  if not url:
978
- return
1005
+ url = os.environ.get('SINGLESTOREDB_URL')
1006
+ if not url:
1007
+ return
979
1008
 
980
1009
  out = {}
981
1010
  urlp = connection._parse_url(url)
@@ -7,6 +7,7 @@ from ._objects import secrets # noqa: F401
7
7
  from ._objects import stage # noqa: F401
8
8
  from ._objects import workspace # noqa: F401
9
9
  from ._objects import workspace_group # noqa: F401
10
+ from ._portal import portal # noqa: F401
10
11
 
11
12
  if 'SINGLESTOREDB_ORGANIZATION' not in _os.environ:
12
13
  _warnings.warn(
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env python
2
+ import json
3
+ import os
4
+ import re
5
+ import time
6
+ import urllib.parse
7
+ from typing import Any
8
+ from typing import Callable
9
+ from typing import Dict
10
+ from typing import List
11
+ from typing import Optional
12
+
13
+ from . import _objects as obj
14
+ from ..management import workspace as mgr
15
+ from ..utils import events
16
+
17
+ try:
18
+ from IPython import display
19
+ has_ipython = True
20
+ except ImportError:
21
+ has_ipython = False
22
+
23
+
24
+ class Portal(object):
25
+ """SingleStore Portal information."""
26
+
27
+ def __init__(self) -> None:
28
+ self._connection_info: Dict[str, Any] = {}
29
+ self._authentication_info: Dict[str, Any] = {}
30
+ self._theme_info: Dict[str, Any] = {}
31
+ events.subscribe(self._request)
32
+
33
+ def __str__(self) -> str:
34
+ attrs = []
35
+ for name in [
36
+ 'organization_id', 'workspace_group_id', 'workspace_id',
37
+ 'host', 'port', 'user', 'password', 'default_database',
38
+ ]:
39
+ if name == 'password':
40
+ if self.password is not None:
41
+ attrs.append("password='***'")
42
+ else:
43
+ attrs.append('password=None')
44
+ else:
45
+ attrs.append(f'{name}={getattr(self, name)!r}')
46
+ return f'{type(self).__name__}({", ".join(attrs)})'
47
+
48
+ def __repr__(self) -> str:
49
+ return str(self)
50
+
51
+ def _call_javascript(
52
+ self,
53
+ func: str,
54
+ args: Optional[List[Any]] = None,
55
+ wait_on_condition: Optional[Callable[[], bool]] = None,
56
+ timeout_message: str = 'timed out waiting on condition',
57
+ wait_interval: float = 0.2,
58
+ timeout: float = 5.0,
59
+ ) -> None:
60
+ if not has_ipython or not func:
61
+ return
62
+
63
+ if not re.match(r'^[A-Z_][\w\._]*$', func, flags=re.I):
64
+ raise ValueError(f'function name is not valid: {func}')
65
+
66
+ args = args if args else []
67
+
68
+ code = f'''
69
+ if (window.singlestore && window.singlestore.portal) {{
70
+ window.singlestore.portal.{func}.apply(
71
+ window,
72
+ JSON.parse({repr(json.dumps(args))})
73
+ )
74
+ }}
75
+ '''
76
+
77
+ display.display(display.Javascript(code))
78
+
79
+ if wait_on_condition is not None:
80
+ elapsed = 0.0
81
+ while True:
82
+ if wait_on_condition():
83
+ break
84
+ if elapsed > timeout:
85
+ raise RuntimeError(timeout_message)
86
+ time.sleep(wait_interval)
87
+ elapsed += wait_interval
88
+
89
+ def _request(self, msg: Dict[str, Any]) -> None:
90
+ """Handle request on the control stream."""
91
+ func = getattr(self, '_handle_' + msg.get('name', 'unknown').split('.')[-1])
92
+ if func is not None:
93
+ func(msg.get('data', {}))
94
+
95
+ def _handle_connection_updated(self, data: Dict[str, Any]) -> None:
96
+ """Handle connection_updated event."""
97
+ self._connection_info = dict(data)
98
+
99
+ def _handle_authentication_updated(self, data: Dict[str, Any]) -> None:
100
+ """Handle authentication_updated event."""
101
+ self._authentication_info = dict(data)
102
+
103
+ def _handle_theme_updated(self, data: Dict[str, Any]) -> None:
104
+ """Handle theme_updated event."""
105
+ self._theme_info = dict(data)
106
+
107
+ def _handle_unknown(self, data: Dict[str, Any]) -> None:
108
+ """Handle unknown events."""
109
+ pass
110
+
111
+ @property
112
+ def organization_id(self) -> Optional[str]:
113
+ """Organization ID."""
114
+ try:
115
+ return self._connection_info['organization']
116
+ except KeyError:
117
+ return os.environ.get('SINGLESTOREDB_ORGANIZATION')
118
+
119
+ @property
120
+ def organization(self) -> obj.Organization:
121
+ """Organization."""
122
+ return obj.organization
123
+
124
+ @property
125
+ def stage(self) -> obj.Stage:
126
+ """Stage."""
127
+ return obj.stage
128
+
129
+ @property
130
+ def secrets(self) -> obj.Secrets:
131
+ """Secrets."""
132
+ return obj.secrets
133
+
134
+ @property
135
+ def workspace_group_id(self) -> Optional[str]:
136
+ """Workspace Group ID."""
137
+ try:
138
+ return self._connection_info['workspace_group']
139
+ except KeyError:
140
+ return os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP')
141
+
142
+ @property
143
+ def workspace_group(self) -> obj.WorkspaceGroup:
144
+ """Workspace group."""
145
+ return obj.workspace_group
146
+
147
+ @workspace_group.setter
148
+ def workspace_group(self) -> None:
149
+ """Set workspace group."""
150
+ raise AttributeError(
151
+ 'workspace group can not be set explictly; ' +
152
+ 'you can only set a workspace',
153
+ )
154
+
155
+ @property
156
+ def workspace_id(self) -> Optional[str]:
157
+ """Workspace ID."""
158
+ try:
159
+ return self._connection_info['workspace']
160
+ except KeyError:
161
+ return os.environ.get('SINGLESTOREDB_WORKSPACE')
162
+
163
+ @property
164
+ def workspace(self) -> obj.Workspace:
165
+ """Workspace."""
166
+ return obj.workspace
167
+
168
+ @workspace.setter
169
+ def workspace(self, name_or_id: str) -> None:
170
+ """Set workspace."""
171
+ if re.match(
172
+ r'[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}',
173
+ name_or_id, flags=re.I,
174
+ ):
175
+ w = mgr.get_workspace(name_or_id)
176
+ else:
177
+ w = mgr.get_workspace_group(self.workspace_group_id).workspaces[name_or_id]
178
+
179
+ if w.state and w.state.lower() not in ['active', 'resumed']:
180
+ raise RuntimeError('workspace is not active')
181
+
182
+ id = w.id
183
+
184
+ self._call_javascript(
185
+ 'changeWorkspace', [id],
186
+ wait_on_condition=lambda: self.workspace_id == id, # type: ignore
187
+ timeout_message='timeout waiting for workspace update',
188
+ )
189
+
190
+ @property
191
+ def cluster_id(self) -> Optional[str]:
192
+ """Cluster ID."""
193
+ try:
194
+ return self._connection_info['cluster']
195
+ except KeyError:
196
+ return os.environ.get('SINGLESTOREDB_CLUSTER')
197
+
198
+ def _parse_url(self) -> Dict[str, Any]:
199
+ url = urllib.parse.urlparse(
200
+ os.environ.get('SINGLESTOREDB_URL', ''),
201
+ )
202
+ return dict(
203
+ host=url.hostname or None,
204
+ port=url.port or None,
205
+ user=url.username or None,
206
+ password=url.password or None,
207
+ default_database=url.path.split('/')[-1] or None,
208
+ )
209
+
210
+ @property
211
+ def connection_url(self) -> Optional[str]:
212
+ """Connection URL."""
213
+ try:
214
+ return self._connection_info['connection_url']
215
+ except KeyError:
216
+ return os.environ.get('SINGLESTOREDB_URL')
217
+
218
+ @property
219
+ def connection_url_kai(self) -> Optional[str]:
220
+ """Kai connectionURL."""
221
+ try:
222
+ return self._connection_info.get('connection_url_kai')
223
+ except KeyError:
224
+ return os.environ.get('SINGLESTOREDB_URL_KAI')
225
+
226
+ @property
227
+ def host(self) -> Optional[str]:
228
+ """Hostname."""
229
+ try:
230
+ return self._connection_info['host']
231
+ except KeyError:
232
+ return self._parse_url()['host']
233
+
234
+ @property
235
+ def port(self) -> Optional[int]:
236
+ """Database server port."""
237
+ try:
238
+ return self._connection_info['port']
239
+ except KeyError:
240
+ return self._parse_url()['port']
241
+
242
+ @property
243
+ def user(self) -> Optional[str]:
244
+ """Username."""
245
+ try:
246
+ return self._authentication_info['user']
247
+ except KeyError:
248
+ return self._parse_url()['user']
249
+
250
+ @property
251
+ def password(self) -> Optional[str]:
252
+ """Password."""
253
+ try:
254
+ return self._authentication_info['password']
255
+ except KeyError:
256
+ return self._parse_url()['password']
257
+
258
+ @property
259
+ def default_database(self) -> Optional[str]:
260
+ """Default database."""
261
+ try:
262
+ return self._connection_info['default_database']
263
+ except KeyError:
264
+ return self._parse_url()['default_database']
265
+
266
+ @default_database.setter
267
+ def default_database(self, name: str) -> None:
268
+ """Set default database."""
269
+ self._call_javascript(
270
+ 'changeDefaultDatabase', [name],
271
+ wait_on_condition=lambda: self.default_database == name, # type: ignore
272
+ timeout_message='timeout waiting for database update',
273
+ )
274
+
275
+ @property
276
+ def version(self) -> Optional[str]:
277
+ """Version."""
278
+ return self._connection_info.get('version')
279
+
280
+
281
+ portal = Portal()
@@ -1849,8 +1849,6 @@ class TestConnection(unittest.TestCase):
1849
1849
 
1850
1850
  bits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
1851
1851
 
1852
- print(row)
1853
-
1854
1852
  assert row['id'] == 0, row['id']
1855
1853
  assert row['tinyint'] == 80, row['tinyint']
1856
1854
  assert row['unsigned_tinyint'] == 85, row['unsigned_tinyint']
@@ -2888,9 +2886,10 @@ class TestConnection(unittest.TestCase):
2888
2886
  self.cur.execute('select a from f32_vectors order by id')
2889
2887
  out = list(self.cur)
2890
2888
 
2891
- assert out[0][0].dtype is np.dtype('float32')
2892
- assert out[1][0].dtype is np.dtype('float32')
2893
- assert out[2][0].dtype is np.dtype('float32')
2889
+ if hasattr(out[0][0], 'dtype'):
2890
+ assert out[0][0].dtype is np.dtype('float32')
2891
+ assert out[1][0].dtype is np.dtype('float32')
2892
+ assert out[2][0].dtype is np.dtype('float32')
2894
2893
 
2895
2894
  np.testing.assert_array_equal(
2896
2895
  out[0][0],
@@ -2917,9 +2916,10 @@ class TestConnection(unittest.TestCase):
2917
2916
  self.cur.execute('select a from f64_vectors order by id')
2918
2917
  out = list(self.cur)
2919
2918
 
2920
- assert out[0][0].dtype is np.dtype('float64')
2921
- assert out[1][0].dtype is np.dtype('float64')
2922
- assert out[2][0].dtype is np.dtype('float64')
2919
+ if hasattr(out[0][0], 'dtype'):
2920
+ assert out[0][0].dtype is np.dtype('float64')
2921
+ assert out[1][0].dtype is np.dtype('float64')
2922
+ assert out[2][0].dtype is np.dtype('float64')
2923
2923
 
2924
2924
  np.testing.assert_array_equal(
2925
2925
  out[0][0],
@@ -2946,9 +2946,10 @@ class TestConnection(unittest.TestCase):
2946
2946
  self.cur.execute('select a from i8_vectors order by id')
2947
2947
  out = list(self.cur)
2948
2948
 
2949
- assert out[0][0].dtype is np.dtype('int8')
2950
- assert out[1][0].dtype is np.dtype('int8')
2951
- assert out[2][0].dtype is np.dtype('int8')
2949
+ if hasattr(out[0][0], 'dtype'):
2950
+ assert out[0][0].dtype is np.dtype('int8')
2951
+ assert out[1][0].dtype is np.dtype('int8')
2952
+ assert out[2][0].dtype is np.dtype('int8')
2952
2953
 
2953
2954
  np.testing.assert_array_equal(
2954
2955
  out[0][0],
@@ -2975,9 +2976,10 @@ class TestConnection(unittest.TestCase):
2975
2976
  self.cur.execute('select a from i16_vectors order by id')
2976
2977
  out = list(self.cur)
2977
2978
 
2978
- assert out[0][0].dtype is np.dtype('int16')
2979
- assert out[1][0].dtype is np.dtype('int16')
2980
- assert out[2][0].dtype is np.dtype('int16')
2979
+ if hasattr(out[0][0], 'dtype'):
2980
+ assert out[0][0].dtype is np.dtype('int16')
2981
+ assert out[1][0].dtype is np.dtype('int16')
2982
+ assert out[2][0].dtype is np.dtype('int16')
2981
2983
 
2982
2984
  np.testing.assert_array_equal(
2983
2985
  out[0][0],
@@ -3004,9 +3006,10 @@ class TestConnection(unittest.TestCase):
3004
3006
  self.cur.execute('select a from i32_vectors order by id')
3005
3007
  out = list(self.cur)
3006
3008
 
3007
- assert out[0][0].dtype is np.dtype('int32')
3008
- assert out[1][0].dtype is np.dtype('int32')
3009
- assert out[2][0].dtype is np.dtype('int32')
3009
+ if hasattr(out[0][0], 'dtype'):
3010
+ assert out[0][0].dtype is np.dtype('int32')
3011
+ assert out[1][0].dtype is np.dtype('int32')
3012
+ assert out[2][0].dtype is np.dtype('int32')
3010
3013
 
3011
3014
  np.testing.assert_array_equal(
3012
3015
  out[0][0],
@@ -3033,9 +3036,10 @@ class TestConnection(unittest.TestCase):
3033
3036
  self.cur.execute('select a from i64_vectors order by id')
3034
3037
  out = list(self.cur)
3035
3038
 
3036
- assert out[0][0].dtype is np.dtype('int64')
3037
- assert out[1][0].dtype is np.dtype('int64')
3038
- assert out[2][0].dtype is np.dtype('int64')
3039
+ if hasattr(out[0][0], 'dtype'):
3040
+ assert out[0][0].dtype is np.dtype('int64')
3041
+ assert out[1][0].dtype is np.dtype('int64')
3042
+ assert out[2][0].dtype is np.dtype('int64')
3039
3043
 
3040
3044
  np.testing.assert_array_equal(
3041
3045
  out[0][0],
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env python
2
+ from typing import Any
3
+ from typing import Callable
4
+ from typing import Dict
5
+ from typing import Set
6
+
7
+ try:
8
+ from IPython import get_ipython
9
+ has_ipython = True
10
+ except ImportError:
11
+ has_ipython = False
12
+
13
+
14
+ _subscribers: Set[Callable[[Dict[str, Any]], None]] = set()
15
+
16
+
17
+ def subscribe(func: Callable[[Dict[str, Any]], None]) -> None:
18
+ """
19
+ Subscribe to SingleStore portal events.
20
+
21
+ Parameters
22
+ ----------
23
+ func : Callable
24
+ The function to call when an event is received
25
+
26
+ """
27
+ _subscribers.add(func)
28
+
29
+
30
+ def _event_handler(stream: Any, ident: Any, msg: Dict[str, Any]) -> None:
31
+ """Handle request on the control stream."""
32
+ if not _subscribers or not isinstance(msg, dict):
33
+ return
34
+
35
+ content = msg.get('content', {})
36
+ if content.get('type', '') != 'event':
37
+ return
38
+
39
+ for func in _subscribers:
40
+ func(content)
41
+
42
+
43
+ # Inject a control handler to receive SingleStore events
44
+ if has_ipython:
45
+ try:
46
+ _handlers = get_ipython().kernel.control_handlers
47
+ _handlers['singlestore_portal_request'] = _event_handler
48
+ except AttributeError:
49
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.3.1
3
+ Version: 1.4.1
4
4
  Summary: Interface to the SingleStoreDB database and workspace management APIs
5
5
  Home-page: https://github.com/singlestore-labs/singlestoredb-python
6
6
  Author: SingleStore
@@ -1,15 +1,15 @@
1
- _singlestoredb_accel.abi3.so,sha256=Gr5Yy0gaZIzCaIUpP4xAazCHP6FAzKxKe6HNjEeH7Ls,206605
2
- singlestoredb-1.3.1.dist-info/RECORD,,
3
- singlestoredb-1.3.1.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
4
- singlestoredb-1.3.1.dist-info/WHEEL,sha256=_VEguvlLpUd-c8RbFMA4yMIVNMBv2LhpxYLCEQ-Bogk,113
5
- singlestoredb-1.3.1.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
6
- singlestoredb-1.3.1.dist-info/top_level.txt,sha256=SDtemIXf-Kp-_F2f_S6x0db33cHGOILdAEsIQZe2LZc,35
7
- singlestoredb-1.3.1.dist-info/METADATA,sha256=ur6IwQQDYz7HieIdCT1dIV2N2nDpApetbvIKHBOYZsc,5570
1
+ _singlestoredb_accel.abi3.so,sha256=Wf5qOAlVEZh3MLHzLkanuIeaN_sdTJ_H6iqy1V8a_fc,206633
2
+ singlestoredb-1.4.1.dist-info/RECORD,,
3
+ singlestoredb-1.4.1.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
4
+ singlestoredb-1.4.1.dist-info/WHEEL,sha256=_VEguvlLpUd-c8RbFMA4yMIVNMBv2LhpxYLCEQ-Bogk,113
5
+ singlestoredb-1.4.1.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
6
+ singlestoredb-1.4.1.dist-info/top_level.txt,sha256=SDtemIXf-Kp-_F2f_S6x0db33cHGOILdAEsIQZe2LZc,35
7
+ singlestoredb-1.4.1.dist-info/METADATA,sha256=e3LgU-lYTxOTjgFy24CcG69A9Vvq6Ke5YV0bV0oT2XA,5570
8
8
  singlestoredb/auth.py,sha256=u8D9tpKzrqa4ssaHjyZnGDX1q8XBpGtuoOkTkSv7B28,7599
9
9
  singlestoredb/config.py,sha256=H4pQxqBEGGCmVHg40VEnAdqGXHun8ougZzj-Ed6ZLH4,11822
10
- singlestoredb/__init__.py,sha256=tIRnEi_9ybtqqhzIJI6qHSNzCnvajFbb7LAvOMx-mzA,1634
10
+ singlestoredb/__init__.py,sha256=P2EmifYW3g7dPP3gIrZyeec6uJpf9rZUbYwUeW_YGTE,1634
11
11
  singlestoredb/types.py,sha256=FIqO1A7e0Gkk7ITmIysBy-P5S--ItbMSlYvblzqGS30,9969
12
- singlestoredb/connection.py,sha256=aLc9_9q5pJ2DCHbmBkWP-4KlDPGoCyB92yaQfNtcnSA,44958
12
+ singlestoredb/connection.py,sha256=WL_TSSHhhjLV0pnJOtaeMAf0o1DI-PJv9xnLFf_cM08,45173
13
13
  singlestoredb/pytest.py,sha256=OyF3BO9mgxenifYhOihnzGk8WzCJ_zN5_mxe8XyFPOc,9074
14
14
  singlestoredb/exceptions.py,sha256=HuoA6sMRL5qiCiee-_5ddTGmFbYC9Euk8TYUsh5GvTw,3234
15
15
  singlestoredb/converters.py,sha256=t1hRMZfccWJs_WyOw-W-Kh87fxsOkpOnKXAeh_Nr-zU,20681
@@ -29,7 +29,7 @@ singlestoredb/tests/test_fusion.py,sha256=UPaxXt5YNa3GS44l4oZfmUcq89YgN7EWWIbW_o
29
29
  singlestoredb/tests/test_plugin.py,sha256=qpO9wmWc62VaijN1sJ97YSYIX7I7Y5C6sY-WzwrutDQ,812
30
30
  singlestoredb/tests/test_basics.py,sha256=rUfUGZ54xybvgp11XYWdqnUYMKa6VckB3XkX9LFnxRw,44180
31
31
  singlestoredb/tests/test_ext_func.py,sha256=OWd-CJ1Owhx72nikSWWEF2EQFCJk7vEXZM2Oy9EbYQo,37357
32
- singlestoredb/tests/test_connection.py,sha256=2NZSUwIRNyCw2A9WoIlisxbQc7PcA8xYoQEztTPTaHY,117405
32
+ singlestoredb/tests/test_connection.py,sha256=ZmhnECusipQAuc6bfZOfMMZULrtxUhnxjxNNSSi6xH4,117697
33
33
  singlestoredb/tests/test_ext_func_data.py,sha256=yTADD93nPxX6_rZXXLZaOWEI_yPvYyir9psn5PK9ctU,47695
34
34
  singlestoredb/tests/test_exceptions.py,sha256=tfr_8X2w1UmG4nkSBzWGB0C7ehrf1GAVgj6_ODaG-TM,1131
35
35
  singlestoredb/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -46,14 +46,15 @@ singlestoredb/tests/test2.sql,sha256=D4U2GSlOVeo39U8-RMM4YziJzYFfi4Ztm2YXJVJVAS8
46
46
  singlestoredb/tests/ext_funcs/__init__.py,sha256=qZLnDI_Ck0tguVi-K-BKXDHAcC0jui3dsm93Djj4x08,9290
47
47
  singlestoredb/management/organization.py,sha256=Y0JFSxYF_UOjip53gcbBXWPCe_t92zo3d99jZNrhkx4,4995
48
48
  singlestoredb/management/region.py,sha256=HnLcWUh7r_aLECliplCDHak4a_F3B7LOSXEYMW66qD0,1611
49
- singlestoredb/management/__init__.py,sha256=puMnH8S3BWEgIlAmuLLh_ZqTN1jml7E8Mme30vkmPOQ,235
50
- singlestoredb/management/utils.py,sha256=QKagKjAhezmBaGfNh81ncbA321mMV243cUY4CUMdcK8,9197
51
- singlestoredb/management/cluster.py,sha256=_TT4tV43VPDrtcdS_VN-TTYij7yFQzjAMeuYRF9zKj8,14362
52
- singlestoredb/management/workspace.py,sha256=ie93R8_fWGaHgxByUk5hKVkcc9VEbDY4kf5nm68dN1U,61642
53
- singlestoredb/management/manager.py,sha256=m8I5zTmEqjMCEE4fmmVdzza8TvofhnIHvO0np0WH-Y8,8810
49
+ singlestoredb/management/__init__.py,sha256=mhWXjLhp5-t8dhl0vl7SjayKrvJlDb5_hl1YWvDgiMA,237
50
+ singlestoredb/management/utils.py,sha256=nTsGTDgl8lmjwcfl2nMS6feRn5P-jUjHlkVGzG5qrXM,10783
51
+ singlestoredb/management/cluster.py,sha256=i23Smr1PBrDZ8NO_VPd_-bEYkyHvVe9CCRGUjHn_1yQ,14362
52
+ singlestoredb/management/workspace.py,sha256=9oamNIaE5vLZNAlpl5SK-xu27qPqx2Ff3ZIdKckNfmc,61673
53
+ singlestoredb/management/manager.py,sha256=sFP1vZGS8WpN8E0XLu1N7Mxtq6Sixalln44HlTQEyXI,8800
54
54
  singlestoredb/management/billing_usage.py,sha256=9ighjIpcopgIyJOktBYQ6pahBZmWGHOPyyCW4gu9FGs,3735
55
55
  singlestoredb/utils/config.py,sha256=m3Xn6hsbdKyLufSnbokhFJ9Vfaz9Qpkj1IEnIiH9oJQ,24503
56
56
  singlestoredb/utils/results.py,sha256=bJtaUaDiFq26IsPAKZ2FHGB7csMn94EAxLKrP4HaEEA,15277
57
+ singlestoredb/utils/events.py,sha256=9IB84T3pKQjs7aaoSSJCw7soNngnhoTDWIC52M51R9Y,1139
57
58
  singlestoredb/utils/convert_rows.py,sha256=A6up7a8Bq-eV2BXdGCotQviqp1Q7XdJ2MA9339hLYVQ,1816
58
59
  singlestoredb/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
60
  singlestoredb/utils/dtypes.py,sha256=1qUiB4BJFJ7rOVh2mItQssYbJupV7uq1x8uwX-Eu2Ks,5898
@@ -62,11 +63,13 @@ singlestoredb/utils/debug.py,sha256=0JiLA37u_9CKiDGiN9BK_PtFMUku3vIcNjERWaTNRSU,
62
63
  singlestoredb/utils/mogrify.py,sha256=-a56IF70U6CkfadeaZgfjRSVsAD3PuqRrzPpjZlgbwY,4050
63
64
  singlestoredb/http/__init__.py,sha256=A_2ZUCCpvRYIA6YDpPy57wL5R1eZ5SfP6I1To5nfJ2s,912
64
65
  singlestoredb/http/connection.py,sha256=HUYzgiGYRUblHhgDi-ghfXykGkdB9iQ3i_XVvhtqH88,39349
66
+ singlestoredb/ai/__init__.py,sha256=7Pubobzx5OlyepNo5DOOxWev1DUW9WFc9P6Qver2xpY,60
67
+ singlestoredb/ai/embeddings.py,sha256=3jghE4WMf7vy8RobhrMOLvMLnDNGbkPCF48B3fGM38U,746
65
68
  singlestoredb/mysql/protocol.py,sha256=2GG8qTXy5npqo7z2D2K5T0S8PtoUOS-hFDEXy8VConw,14451
66
69
  singlestoredb/mysql/cursors.py,sha256=Eqe7jITRvOo4P_TxIarTumg_2PG1DcCfZ4Uo9IFdDa8,26794
67
70
  singlestoredb/mysql/__init__.py,sha256=olUTAvkiERhDW41JXQMawkg-i0tvBEkoTkII1tt6lxU,4492
68
71
  singlestoredb/mysql/times.py,sha256=2j7purNVnJmjhOUgwUze-r3kNlCWqxjXj-jtqOzBfZI,463
69
- singlestoredb/mysql/connection.py,sha256=px1_uGW1h1zwebYkIXmOjASmEXkc_ZQrLb3JNKfUS0w,70361
72
+ singlestoredb/mysql/connection.py,sha256=Z3xJ-yFY4yc-rMGp2ty7V2NZ6Ixb74UfnlwLcypJ7d4,71446
70
73
  singlestoredb/mysql/charset.py,sha256=-FlONDS_oAUF5B3mIgeHBPb_SCt4zHD33arUeBNctU0,10510
71
74
  singlestoredb/mysql/converters.py,sha256=CVe8SDmjbIAhy1xpQ2N5OKWw6t5eWpw-EU3QTlA0Hh0,7500
72
75
  singlestoredb/mysql/optionfile.py,sha256=DqL-rOQcqQncD5eVbPRkwZqo7Pj3Vh40VLx3E_e87TU,655
@@ -106,7 +109,7 @@ singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py,s
106
109
  singlestoredb/functions/decorator.py,sha256=H12MUeBw8VOppx6esntaR43ukeIffbnAr716CBpYJ4g,5193
107
110
  singlestoredb/functions/__init__.py,sha256=WL1LqgMTdnGOse3tQqmD-HH8TdfCPS89GNO7hO0v_aw,41
108
111
  singlestoredb/functions/dtypes.py,sha256=a2vevIug8NhiUCFiSOKwRPpdWU69Gn13ZoQ6Aovskhc,31408
109
- singlestoredb/functions/signature.py,sha256=fNnlTfc0R0sM9wm78UwG7Ok9eMJTtOfawrIpjts2wdY,18866
112
+ singlestoredb/functions/signature.py,sha256=1CRtE_Dp47ahYoPzB8oCVNS_gmEQz91xDH8FEKqWGL8,18960
110
113
  singlestoredb/functions/ext/asgi.py,sha256=YV4i5RmnSsPvdJPM7_z9X3oR9b1zdZq2ylTSEAM_G54,40017
111
114
  singlestoredb/functions/ext/arrow.py,sha256=WB7n1ACslyd8nlbFzUvlbxn1BVuEjA9-BGBEqCWlSOo,9061
112
115
  singlestoredb/functions/ext/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
@@ -114,6 +117,7 @@ singlestoredb/functions/ext/utils.py,sha256=2-B8YU_Iekv8JcpI-ochs9TIeuyatLaLAH-A
114
117
  singlestoredb/functions/ext/mmap.py,sha256=OB6CIYoLe_AYuJM10lE0I6QhZJ5kMhLNbQo2Sp1wiZA,13711
115
118
  singlestoredb/functions/ext/json.py,sha256=XkI8jirxi1T9F-M0p9NpLezph0MRAhYmDiPuU2Id0Uo,10404
116
119
  singlestoredb/functions/ext/rowdat_1.py,sha256=JgKRsVSQYczFD6cmo2xLilbNPYpyLL2tPOWO1Gh25ow,22306
117
- singlestoredb/notebook/__init__.py,sha256=5_Nfme_Tt6e22LoId6xpaiNcVYPR3pmHXDt7EoX9E6o,491
120
+ singlestoredb/notebook/__init__.py,sha256=v0j1E3MFAtaC8wTrR-F7XY0nytUvQ4XpYhVXddv2xA0,533
118
121
  singlestoredb/notebook/_objects.py,sha256=MkB1eowEq5SQXFHY00xAKAyyeLqHu_uaZiA20BCJPaE,8043
122
+ singlestoredb/notebook/_portal.py,sha256=a0UeRwXQl-tBEfV_IOOL_UK2Zzrs-RgGswvjJiZk8Wc,8485
119
123
  singlestoredb/alchemy/__init__.py,sha256=dXRThusYrs_9GjrhPOw0-vw94in_T8yY9jE7SGCqiQk,2523