singlestoredb 1.4.0__py3-none-any.whl → 1.4.2__py3-none-any.whl

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

Potentially problematic release.


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

singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.4.0'
16
+ __version__ = '1.4.2'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -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 = {}
@@ -28,8 +28,23 @@ CORE_GRAMMAR = r'''
28
28
  eq = ws* "=" ws*
29
29
  open_paren = ws* "(" ws*
30
30
  close_paren = ws* ")" ws*
31
+ open_repeats = ws* ~r"[\(\[\{]" ws*
32
+ close_repeats = ws* ~r"[\)\]\}]" ws*
31
33
  select = ~r"SELECT"i ws+ ~r".+" ws*
32
- '''
34
+
35
+ json = ws* json_object ws*
36
+ json_object = ~r"{\s*" json_members? ~r"\s*}"
37
+ json_members = json_mapping (~r"\s*,\s*" json_mapping)*
38
+ json_mapping = json_string ~r"\s*:\s*" json_value
39
+ json_array = ~r"\[\s*" json_items? ~r"\s*\]"
40
+ json_items = json_value (~r"\s*,\s*" json_value)*
41
+ json_value = json_object / json_array / json_string / json_true_val / json_false_val / json_null_val / json_number
42
+ json_true_val = "true"
43
+ json_false_val = "false"
44
+ json_null_val = "null"
45
+ json_string = ~r"\"[ !#-\[\]-\U0010ffff]*(?:\\(?:[\"\\/bfnrt]|u[0-9A-Fa-f]{4})[ !#-\[\]-\U0010ffff]*)*\""
46
+ json_number = ~r"-?(0|[1-9][0-9]*)(\.\d*)?([eE][-+]?\d+)?"
47
+ ''' # noqa: E501
33
48
 
34
49
  BUILTINS = {
35
50
  '<order-by>': r'''
@@ -47,6 +62,7 @@ BUILTINS = {
47
62
  ''',
48
63
  '<integer>': '',
49
64
  '<number>': '',
65
+ '<json>': '',
50
66
  }
51
67
 
52
68
  BUILTIN_DEFAULTS = { # type: ignore
@@ -54,9 +70,36 @@ BUILTIN_DEFAULTS = { # type: ignore
54
70
  'like': None,
55
71
  'extended': False,
56
72
  'limit': None,
73
+ 'json': {},
74
+ }
75
+
76
+ _json_unesc_re = re.compile(r'\\(["/\\bfnrt]|u[0-9A-Fa-f])')
77
+ _json_unesc_map = {
78
+ '"': '"',
79
+ '/': '/',
80
+ '\\': '\\',
81
+ 'b': '\b',
82
+ 'f': '\f',
83
+ 'n': '\n',
84
+ 'r': '\r',
85
+ 't': '\t',
57
86
  }
58
87
 
59
88
 
89
+ def _json_unescape(m: Any) -> str:
90
+ c = m.group(1)
91
+ if c[0] == 'u':
92
+ return chr(int(c[1:], 16))
93
+ c2 = _json_unesc_map.get(c)
94
+ if not c2:
95
+ raise ValueError(f'invalid escape sequence: {m.group(0)}')
96
+ return c2
97
+
98
+
99
+ def json_unescape(s: str) -> str:
100
+ return _json_unesc_re.sub(_json_unescape, s[1:-1])
101
+
102
+
60
103
  def get_keywords(grammar: str) -> Tuple[str, ...]:
61
104
  """Return all all-caps words from the beginning of the line."""
62
105
  m = re.match(r'^\s*((?:[A-Z0-9_]+)(\s+|$|;))+', grammar)
@@ -89,7 +132,7 @@ def process_alternates(m: Any) -> str:
89
132
  def process_repeats(m: Any) -> str:
90
133
  """Add repeated patterns."""
91
134
  sql = m.group(1).strip()
92
- return f'open_paren? {sql} ws* ( comma {sql} ws* )* close_paren?'
135
+ return f'open_repeats? {sql} ws* ( comma {sql} ws* )* close_repeats?'
93
136
 
94
137
 
95
138
  def lower_and_regex(m: Any) -> str:
@@ -639,6 +682,14 @@ class SQLHandler(NodeVisitor):
639
682
  """Close parenthesis."""
640
683
  return
641
684
 
685
+ def visit_open_repeats(self, node: Node, visited_children: Iterable[Any]) -> Any:
686
+ """Open repeat grouping."""
687
+ return
688
+
689
+ def visit_close_repeats(self, node: Node, visited_children: Iterable[Any]) -> Any:
690
+ """Close repeat grouping."""
691
+ return
692
+
642
693
  def visit_init(self, node: Node, visited_children: Iterable[Any]) -> Any:
643
694
  """Entry point of the grammar."""
644
695
  _, out, *_ = visited_children
@@ -662,6 +713,57 @@ class SQLHandler(NodeVisitor):
662
713
  ascending.append(value[1].upper().startswith('A'))
663
714
  return {'order_by': {'by': by, 'ascending': ascending}}
664
715
 
716
+ def _delimited(self, node: Node, children: Iterable[Any]) -> Any:
717
+ children = list(children)
718
+ items = [children[0]]
719
+ items.extend(item for _, item in children[1])
720
+ return items
721
+
722
+ def _atomic(self, node: Node, children: Iterable[Any]) -> Any:
723
+ return list(children)[0]
724
+
725
+ # visitors
726
+ visit_json_value = _atomic
727
+ visit_json_members = visit_json_items = _delimited
728
+
729
+ def visit_json_object(self, node: Node, children: Iterable[Any]) -> Any:
730
+ _, members, _ = children
731
+ if isinstance(members, list):
732
+ members = members[0]
733
+ else:
734
+ members = []
735
+ members = [x for x in members if x != '']
736
+ return dict(members)
737
+
738
+ def visit_json_array(self, node: Node, children: Iterable[Any]) -> Any:
739
+ _, values, _ = children
740
+ if isinstance(values, list):
741
+ values = values[0]
742
+ else:
743
+ values = []
744
+ return values
745
+
746
+ def visit_json_mapping(self, node: Node, children: Iterable[Any]) -> Any:
747
+ key, _, value = children
748
+ return key, value
749
+
750
+ def visit_json_string(self, node: Node, children: Iterable[Any]) -> Any:
751
+ return json_unescape(node.text)
752
+
753
+ def visit_json_number(self, node: Node, children: Iterable[Any]) -> Any:
754
+ if '.' in node.text:
755
+ return float(node.text)
756
+ return int(node.text)
757
+
758
+ def visit_json_true_val(self, node: Node, children: Iterable[Any]) -> Any:
759
+ return True
760
+
761
+ def visit_json_false_val(self, node: Node, children: Iterable[Any]) -> Any:
762
+ return False
763
+
764
+ def visit_json_null_val(self, node: Node, children: Iterable[Any]) -> Any:
765
+ return None
766
+
665
767
  def generic_visit(self, node: Node, visited_children: Iterable[Any]) -> Any:
666
768
  """
667
769
  Handle all undefined rules.
@@ -675,6 +777,9 @@ class SQLHandler(NodeVisitor):
675
777
  rule can have repeated values, a list of values is returned.
676
778
 
677
779
  """
780
+ if node.expr_name.startswith('json'):
781
+ return visited_children or node.text
782
+
678
783
  # Call a grammar rule
679
784
  if node.expr_name in type(self).rule_info:
680
785
  n_keywords = type(self).rule_info[node.expr_name]['n_keywords']
@@ -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
+ 'changeDeployment', [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.4.0
3
+ Version: 1.4.2
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,7 +1,7 @@
1
- singlestoredb/__init__.py,sha256=SiAqHTUbyK9Lcd_4o_x53u-uxkWZAE-XW3YM2zp4CqA,1634
1
+ singlestoredb/__init__.py,sha256=SfeLqsR2jOT4we91ZMxK9oJ2C9sUVj10O_kZ_Z2SyOo,1634
2
2
  singlestoredb/auth.py,sha256=u8D9tpKzrqa4ssaHjyZnGDX1q8XBpGtuoOkTkSv7B28,7599
3
3
  singlestoredb/config.py,sha256=H4pQxqBEGGCmVHg40VEnAdqGXHun8ougZzj-Ed6ZLH4,11822
4
- singlestoredb/connection.py,sha256=aLc9_9q5pJ2DCHbmBkWP-4KlDPGoCyB92yaQfNtcnSA,44958
4
+ singlestoredb/connection.py,sha256=WL_TSSHhhjLV0pnJOtaeMAf0o1DI-PJv9xnLFf_cM08,45173
5
5
  singlestoredb/converters.py,sha256=t1hRMZfccWJs_WyOw-W-Kh87fxsOkpOnKXAeh_Nr-zU,20681
6
6
  singlestoredb/exceptions.py,sha256=HuoA6sMRL5qiCiee-_5ddTGmFbYC9Euk8TYUsh5GvTw,3234
7
7
  singlestoredb/pytest.py,sha256=OyF3BO9mgxenifYhOihnzGk8WzCJ_zN5_mxe8XyFPOc,9074
@@ -12,7 +12,7 @@ singlestoredb/alchemy/__init__.py,sha256=dXRThusYrs_9GjrhPOw0-vw94in_T8yY9jE7SGC
12
12
  singlestoredb/functions/__init__.py,sha256=WL1LqgMTdnGOse3tQqmD-HH8TdfCPS89GNO7hO0v_aw,41
13
13
  singlestoredb/functions/decorator.py,sha256=H12MUeBw8VOppx6esntaR43ukeIffbnAr716CBpYJ4g,5193
14
14
  singlestoredb/functions/dtypes.py,sha256=a2vevIug8NhiUCFiSOKwRPpdWU69Gn13ZoQ6Aovskhc,31408
15
- singlestoredb/functions/signature.py,sha256=fNnlTfc0R0sM9wm78UwG7Ok9eMJTtOfawrIpjts2wdY,18866
15
+ singlestoredb/functions/signature.py,sha256=1CRtE_Dp47ahYoPzB8oCVNS_gmEQz91xDH8FEKqWGL8,18960
16
16
  singlestoredb/functions/ext/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
17
17
  singlestoredb/functions/ext/arrow.py,sha256=WB7n1ACslyd8nlbFzUvlbxn1BVuEjA9-BGBEqCWlSOo,9061
18
18
  singlestoredb/functions/ext/asgi.py,sha256=YV4i5RmnSsPvdJPM7_z9X3oR9b1zdZq2ylTSEAM_G54,40017
@@ -22,7 +22,7 @@ singlestoredb/functions/ext/rowdat_1.py,sha256=JgKRsVSQYczFD6cmo2xLilbNPYpyLL2tP
22
22
  singlestoredb/functions/ext/utils.py,sha256=2-B8YU_Iekv8JcpI-ochs9TIeuyatLaLAH-AyYyUUIg,5311
23
23
  singlestoredb/fusion/__init__.py,sha256=Qo7SuqGw-l-vE8-EI2jhm6hXJkYfOLUKIws9c7LFNX0,356
24
24
  singlestoredb/fusion/graphql.py,sha256=ZA3HcDq5rER-dCEavwTqnF7KM0D2LCYIY7nLQk7lSso,5207
25
- singlestoredb/fusion/handler.py,sha256=hXvJoauKnixfU67IgobmuZF4WKchSzzhUwsPQf_FWT4,21581
25
+ singlestoredb/fusion/handler.py,sha256=wuNq-nzzBngSha_kZ_Da7V7VzQzmhgXgUfh2V0HXLVI,24976
26
26
  singlestoredb/fusion/registry.py,sha256=jjdRTYZ3ylhy6gAoW5xBj0tkxGFBT-2yLQ0tztTgDIY,6112
27
27
  singlestoredb/fusion/result.py,sha256=Bd3KbRpqWqQcWp_Chd4bzBy8Kfc8nXLS_Pn_GGbPO6o,11772
28
28
  singlestoredb/fusion/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -31,18 +31,18 @@ singlestoredb/fusion/handlers/utils.py,sha256=oYbf13Y3orEkJfHMNnO7B_W1anEdK-0S9v
31
31
  singlestoredb/fusion/handlers/workspace.py,sha256=EGxu-dAWNfHsknMNSk1n9cri_zhlMJap4ypywPFiMZQ,25000
32
32
  singlestoredb/http/__init__.py,sha256=A_2ZUCCpvRYIA6YDpPy57wL5R1eZ5SfP6I1To5nfJ2s,912
33
33
  singlestoredb/http/connection.py,sha256=HUYzgiGYRUblHhgDi-ghfXykGkdB9iQ3i_XVvhtqH88,39349
34
- singlestoredb/management/__init__.py,sha256=puMnH8S3BWEgIlAmuLLh_ZqTN1jml7E8Mme30vkmPOQ,235
34
+ singlestoredb/management/__init__.py,sha256=mhWXjLhp5-t8dhl0vl7SjayKrvJlDb5_hl1YWvDgiMA,237
35
35
  singlestoredb/management/billing_usage.py,sha256=9ighjIpcopgIyJOktBYQ6pahBZmWGHOPyyCW4gu9FGs,3735
36
- singlestoredb/management/cluster.py,sha256=_TT4tV43VPDrtcdS_VN-TTYij7yFQzjAMeuYRF9zKj8,14362
37
- singlestoredb/management/manager.py,sha256=m8I5zTmEqjMCEE4fmmVdzza8TvofhnIHvO0np0WH-Y8,8810
36
+ singlestoredb/management/cluster.py,sha256=i23Smr1PBrDZ8NO_VPd_-bEYkyHvVe9CCRGUjHn_1yQ,14362
37
+ singlestoredb/management/manager.py,sha256=sFP1vZGS8WpN8E0XLu1N7Mxtq6Sixalln44HlTQEyXI,8800
38
38
  singlestoredb/management/organization.py,sha256=Y0JFSxYF_UOjip53gcbBXWPCe_t92zo3d99jZNrhkx4,4995
39
39
  singlestoredb/management/region.py,sha256=HnLcWUh7r_aLECliplCDHak4a_F3B7LOSXEYMW66qD0,1611
40
- singlestoredb/management/utils.py,sha256=QKagKjAhezmBaGfNh81ncbA321mMV243cUY4CUMdcK8,9197
41
- singlestoredb/management/workspace.py,sha256=ie93R8_fWGaHgxByUk5hKVkcc9VEbDY4kf5nm68dN1U,61642
40
+ singlestoredb/management/utils.py,sha256=nTsGTDgl8lmjwcfl2nMS6feRn5P-jUjHlkVGzG5qrXM,10783
41
+ singlestoredb/management/workspace.py,sha256=9oamNIaE5vLZNAlpl5SK-xu27qPqx2Ff3ZIdKckNfmc,61673
42
42
  singlestoredb/mysql/__init__.py,sha256=olUTAvkiERhDW41JXQMawkg-i0tvBEkoTkII1tt6lxU,4492
43
43
  singlestoredb/mysql/_auth.py,sha256=AugRitoUwgRIDFuJxuAH4MWIAmckY7Ji2pP6r_Ng9dY,8043
44
44
  singlestoredb/mysql/charset.py,sha256=-FlONDS_oAUF5B3mIgeHBPb_SCt4zHD33arUeBNctU0,10510
45
- singlestoredb/mysql/connection.py,sha256=px1_uGW1h1zwebYkIXmOjASmEXkc_ZQrLb3JNKfUS0w,70361
45
+ singlestoredb/mysql/connection.py,sha256=Z3xJ-yFY4yc-rMGp2ty7V2NZ6Ixb74UfnlwLcypJ7d4,71446
46
46
  singlestoredb/mysql/converters.py,sha256=CVe8SDmjbIAhy1xpQ2N5OKWw6t5eWpw-EU3QTlA0Hh0,7500
47
47
  singlestoredb/mysql/cursors.py,sha256=Eqe7jITRvOo4P_TxIarTumg_2PG1DcCfZ4Uo9IFdDa8,26794
48
48
  singlestoredb/mysql/err.py,sha256=-m5rqXi8yhq6b8SCEJ2h0E5Rudh_15dlAU_WbJ1YrM8,2388
@@ -80,8 +80,9 @@ singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py,sha256=E5_jnyZEZ7_m
80
80
  singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py,sha256=szE4Zodgf7YwhkMBOrCvUwhTWppVtaodsqlV-vJ7fmY,3090
81
81
  singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py,sha256=t_OzqsVnj_ReBbmY_wx51ZcWbLz9nASZ0hno-9YeiyQ,8022
82
82
  singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py,sha256=pl0bvuZo_nzAlYOINxRiR-Zi9khz0W2Pc7vP-K3sQYQ,2819
83
- singlestoredb/notebook/__init__.py,sha256=5_Nfme_Tt6e22LoId6xpaiNcVYPR3pmHXDt7EoX9E6o,491
83
+ singlestoredb/notebook/__init__.py,sha256=v0j1E3MFAtaC8wTrR-F7XY0nytUvQ4XpYhVXddv2xA0,533
84
84
  singlestoredb/notebook/_objects.py,sha256=MkB1eowEq5SQXFHY00xAKAyyeLqHu_uaZiA20BCJPaE,8043
85
+ singlestoredb/notebook/_portal.py,sha256=08_8hFFuD9Ybq3K8ruHOgMhDqzdhjj7o0Sq368zH714,8486
85
86
  singlestoredb/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
87
  singlestoredb/tests/empty.sql,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
88
  singlestoredb/tests/local_infile.csv,sha256=sBtqjvfkS9aoOVx8nMXYgYv4rDuT4OuYhqUhNRu0O68,42
@@ -89,7 +90,7 @@ singlestoredb/tests/test.sql,sha256=dfMehVCQ9wObSVTQKyQi-fRFDZeqRxV4Cj8doBCPEFM,
89
90
  singlestoredb/tests/test2.sql,sha256=D4U2GSlOVeo39U8-RMM4YziJzYFfi4Ztm2YXJVJVAS8,37
90
91
  singlestoredb/tests/test_basics.py,sha256=rUfUGZ54xybvgp11XYWdqnUYMKa6VckB3XkX9LFnxRw,44180
91
92
  singlestoredb/tests/test_config.py,sha256=63lyIQ2KrvGE6C9403B_4Mc90mX4tp42ys5Bih2sXrE,11184
92
- singlestoredb/tests/test_connection.py,sha256=2NZSUwIRNyCw2A9WoIlisxbQc7PcA8xYoQEztTPTaHY,117405
93
+ singlestoredb/tests/test_connection.py,sha256=ZmhnECusipQAuc6bfZOfMMZULrtxUhnxjxNNSSi6xH4,117697
93
94
  singlestoredb/tests/test_dbapi.py,sha256=IKq5Hcwx8WikASP8_AB5fo3TXv7ryWPCVGonoly00gI,652
94
95
  singlestoredb/tests/test_exceptions.py,sha256=tfr_8X2w1UmG4nkSBzWGB0C7ehrf1GAVgj6_ODaG-TM,1131
95
96
  singlestoredb/tests/test_ext_func.py,sha256=OWd-CJ1Owhx72nikSWWEF2EQFCJk7vEXZM2Oy9EbYQo,37357
@@ -109,12 +110,13 @@ singlestoredb/utils/config.py,sha256=m3Xn6hsbdKyLufSnbokhFJ9Vfaz9Qpkj1IEnIiH9oJQ
109
110
  singlestoredb/utils/convert_rows.py,sha256=A6up7a8Bq-eV2BXdGCotQviqp1Q7XdJ2MA9339hLYVQ,1816
110
111
  singlestoredb/utils/debug.py,sha256=0JiLA37u_9CKiDGiN9BK_PtFMUku3vIcNjERWaTNRSU,349
111
112
  singlestoredb/utils/dtypes.py,sha256=1qUiB4BJFJ7rOVh2mItQssYbJupV7uq1x8uwX-Eu2Ks,5898
113
+ singlestoredb/utils/events.py,sha256=9IB84T3pKQjs7aaoSSJCw7soNngnhoTDWIC52M51R9Y,1139
112
114
  singlestoredb/utils/mogrify.py,sha256=-a56IF70U6CkfadeaZgfjRSVsAD3PuqRrzPpjZlgbwY,4050
113
115
  singlestoredb/utils/results.py,sha256=bJtaUaDiFq26IsPAKZ2FHGB7csMn94EAxLKrP4HaEEA,15277
114
116
  singlestoredb/utils/xdict.py,sha256=S9HKgrPrnu_6b7iOwa2KrW8CmU1Uqx0BWdEyogFzWbE,12896
115
- singlestoredb-1.4.0.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
116
- singlestoredb-1.4.0.dist-info/METADATA,sha256=wpLknCKi47UISY50QQ2meZGjhPxpsDMwTIKrQFhKJiA,5570
117
- singlestoredb-1.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
118
- singlestoredb-1.4.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
119
- singlestoredb-1.4.0.dist-info/top_level.txt,sha256=eet8bVPNRqiGeY0PrO5ERH2UpamwlrKHEQCffz4dOh8,14
120
- singlestoredb-1.4.0.dist-info/RECORD,,
117
+ singlestoredb-1.4.2.dist-info/LICENSE,sha256=Mlq78idURT-9G026aMYswwwnnrLcgzTLuXeAs5hjDLM,11341
118
+ singlestoredb-1.4.2.dist-info/METADATA,sha256=zlYnLLjR9xrlJNZv-RBFelx0IM_iinyWKGwoKIC8udI,5570
119
+ singlestoredb-1.4.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
120
+ singlestoredb-1.4.2.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
121
+ singlestoredb-1.4.2.dist-info/top_level.txt,sha256=eet8bVPNRqiGeY0PrO5ERH2UpamwlrKHEQCffz4dOh8,14
122
+ singlestoredb-1.4.2.dist-info/RECORD,,