singlestoredb 1.15.0__cp38-abi3-win32.whl → 1.15.1__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.

Files changed (43) hide show
  1. _singlestoredb_accel.pyd +0 -0
  2. singlestoredb/__init__.py +1 -1
  3. singlestoredb/ai/chat.py +14 -0
  4. singlestoredb/config.py +6 -0
  5. singlestoredb/docstring/__init__.py +33 -0
  6. singlestoredb/docstring/attrdoc.py +126 -0
  7. singlestoredb/docstring/common.py +230 -0
  8. singlestoredb/docstring/epydoc.py +267 -0
  9. singlestoredb/docstring/google.py +412 -0
  10. singlestoredb/docstring/numpydoc.py +562 -0
  11. singlestoredb/docstring/parser.py +100 -0
  12. singlestoredb/docstring/py.typed +1 -0
  13. singlestoredb/docstring/rest.py +256 -0
  14. singlestoredb/docstring/tests/__init__.py +1 -0
  15. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  16. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  17. singlestoredb/docstring/tests/test_google.py +1007 -0
  18. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  19. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  20. singlestoredb/docstring/tests/test_parser.py +248 -0
  21. singlestoredb/docstring/tests/test_rest.py +547 -0
  22. singlestoredb/docstring/tests/test_util.py +70 -0
  23. singlestoredb/docstring/util.py +141 -0
  24. singlestoredb/functions/decorator.py +19 -18
  25. singlestoredb/functions/ext/asgi.py +97 -11
  26. singlestoredb/functions/signature.py +374 -241
  27. singlestoredb/fusion/handlers/files.py +4 -4
  28. singlestoredb/fusion/handlers/models.py +1 -1
  29. singlestoredb/fusion/handlers/stage.py +4 -4
  30. singlestoredb/management/cluster.py +1 -1
  31. singlestoredb/management/manager.py +15 -5
  32. singlestoredb/management/region.py +12 -2
  33. singlestoredb/management/workspace.py +17 -25
  34. singlestoredb/tests/ext_funcs/__init__.py +39 -0
  35. singlestoredb/tests/test_connection.py +18 -8
  36. singlestoredb/tests/test_management.py +24 -57
  37. singlestoredb/tests/test_udf.py +43 -15
  38. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/METADATA +1 -1
  39. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/RECORD +43 -24
  40. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/LICENSE +0 -0
  41. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/WHEEL +0 -0
  42. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/entry_points.txt +0 -0
  43. {singlestoredb-1.15.0.dist-info → singlestoredb-1.15.1.dist-info}/top_level.txt +0 -0
@@ -89,7 +89,7 @@ class ShowPersonalFilesHandler(ShowFilesHandler):
89
89
  specified number.
90
90
  * Use the ``ORDER BY`` clause to sort the results by the specified
91
91
  key. By default, the results are sorted in the ascending order.
92
- * The ``AT PATH`` clause specifies the path in the personal/shared
92
+ * The ``AT`` clause specifies the path in the personal/shared
93
93
  space to list the files from.
94
94
  * Use the ``RECURSIVE`` clause to list the files recursively.
95
95
  * To return more information about the files, use the ``EXTENDED``
@@ -99,7 +99,7 @@ class ShowPersonalFilesHandler(ShowFilesHandler):
99
99
  --------
100
100
  The following command lists the files at a specific path::
101
101
 
102
- SHOW PERSONAL FILES AT PATH "/data/";
102
+ SHOW PERSONAL FILES AT "/data/";
103
103
 
104
104
  The following command lists the files recursively with
105
105
  additional information::
@@ -154,7 +154,7 @@ class ShowSharedFilesHandler(ShowFilesHandler):
154
154
  specified number.
155
155
  * Use the ``ORDER BY`` clause to sort the results by the specified
156
156
  key. By default, the results are sorted in the ascending order.
157
- * The ``AT PATH`` clause specifies the path in the personal/shared
157
+ * The ``AT`` clause specifies the path in the personal/shared
158
158
  space to list the files from.
159
159
  * Use the ``RECURSIVE`` clause to list the files recursively.
160
160
  * To return more information about the files, use the ``EXTENDED``
@@ -164,7 +164,7 @@ class ShowSharedFilesHandler(ShowFilesHandler):
164
164
  --------
165
165
  The following command lists the files at a specific path::
166
166
 
167
- SHOW SHARED FILES AT PATH "/data/";
167
+ SHOW SHARED FILES AT "/data/";
168
168
 
169
169
  The following command lists the files recursively with
170
170
  additional information::
@@ -44,7 +44,7 @@ class ShowModelsHandler(ShowFilesHandler):
44
44
  specified number.
45
45
  * Use the ``ORDER BY`` clause to sort the results by the specified
46
46
  key. By default, the results are sorted in the ascending order.
47
- * The ``AT PATH`` clause specifies the path in the models
47
+ * The ``AT`` clause specifies the path in the models
48
48
  space to list the files from.
49
49
  * To return more information about the files, use the ``EXTENDED``
50
50
  clause.
@@ -62,7 +62,7 @@ class ShowStageFilesHandler(SQLHandler):
62
62
  specified number.
63
63
  * Use the ``ORDER BY`` clause to sort the results by the specified
64
64
  key. By default, the results are sorted in the ascending order.
65
- * The ``AT PATH`` clause specifies the path in the Stage to list
65
+ * The ``AT`` clause specifies the path in the Stage to list
66
66
  the files from.
67
67
  * The ``IN`` clause specifies the ID or the name of the
68
68
  deployment in which the Stage is attached.
@@ -74,7 +74,7 @@ class ShowStageFilesHandler(SQLHandler):
74
74
  --------
75
75
  The following command lists the files at a specific path::
76
76
 
77
- SHOW STAGE FILES IN 'wsg1' AT PATH "/data/";
77
+ SHOW STAGE FILES IN 'wsg1' AT "/data/";
78
78
 
79
79
  The following command lists the files recursively with
80
80
  additional information::
@@ -393,7 +393,7 @@ class DropStageFolderHandler(SQLHandler):
393
393
  # Name of deployment
394
394
  deployment_name = '<deployment-name>'
395
395
 
396
- # Should folers be deleted recursively?
396
+ # Should folders be deleted recursively?
397
397
  recursive = RECURSIVE
398
398
 
399
399
  Description
@@ -474,7 +474,7 @@ class CreateStageFolderHandler(SQLHandler):
474
474
  is created. The path must end with a trailing slash (/).
475
475
  * ``<deployment-id>``: The ID of the deployment in which
476
476
  the Stage is attached.
477
- * ``<deployment-name>``: The name of the deployment in which
477
+ * ``<deployment-name>``: The name of the deployment in
478
478
  which the Stage is attached.
479
479
 
480
480
  Remarks
@@ -390,7 +390,7 @@ class ClusterManager(Manager):
390
390
  :class:`Cluster`
391
391
 
392
392
  """
393
- if isinstance(region, Region):
393
+ if isinstance(region, Region) and region.id:
394
394
  region = region.id
395
395
  res = self._post(
396
396
  'clusters', json=dict(
@@ -148,7 +148,9 @@ class Manager(object):
148
148
 
149
149
  """
150
150
  if self._params:
151
- kwargs['params'] = self._params
151
+ params = dict(self._params)
152
+ params.update(kwargs.get('params', {}))
153
+ kwargs['params'] = params
152
154
  set_organization(kwargs)
153
155
  return self._check(self._doit('get', path, *args, **kwargs), path, kwargs)
154
156
 
@@ -171,7 +173,9 @@ class Manager(object):
171
173
 
172
174
  """
173
175
  if self._params:
174
- kwargs['params'] = self._params
176
+ params = dict(self._params)
177
+ params.update(kwargs.get('params', {}))
178
+ kwargs['params'] = params
175
179
  set_organization(kwargs)
176
180
  return self._check(self._doit('post', path, *args, **kwargs), path, kwargs)
177
181
 
@@ -194,7 +198,9 @@ class Manager(object):
194
198
 
195
199
  """
196
200
  if self._params:
197
- kwargs['params'] = self._params
201
+ params = dict(self._params)
202
+ params.update(kwargs.get('params', {}))
203
+ kwargs['params'] = params
198
204
  set_organization(kwargs)
199
205
  return self._check(self._doit('put', path, *args, **kwargs), path, kwargs)
200
206
 
@@ -217,7 +223,9 @@ class Manager(object):
217
223
 
218
224
  """
219
225
  if self._params:
220
- kwargs['params'] = self._params
226
+ params = dict(self._params)
227
+ params.update(kwargs.get('params', {}))
228
+ kwargs['params'] = params
221
229
  set_organization(kwargs)
222
230
  return self._check(self._doit('delete', path, *args, **kwargs), path, kwargs)
223
231
 
@@ -240,7 +248,9 @@ class Manager(object):
240
248
 
241
249
  """
242
250
  if self._params:
243
- kwargs['params'] = self._params
251
+ params = dict(self._params)
252
+ params.update(kwargs.get('params', {}))
253
+ kwargs['params'] = params
244
254
  set_organization(kwargs)
245
255
  return self._check(self._doit('patch', path, *args, **kwargs), path, kwargs)
246
256
 
@@ -21,7 +21,10 @@ class Region(object):
21
21
 
22
22
  """
23
23
 
24
- def __init__(self, id: str, name: str, provider: str):
24
+ def __init__(
25
+ self, name: str, provider: str, id: Optional[str] = None,
26
+ region_name: Optional[str] = None,
27
+ ) -> None:
25
28
  """Use :attr:`WorkspaceManager.regions` instead."""
26
29
  #: Unique ID of the region
27
30
  self.id = id
@@ -32,6 +35,9 @@ class Region(object):
32
35
  #: Name of the cloud provider
33
36
  self.provider = provider
34
37
 
38
+ #: Name of the provider region
39
+ self.region_name = region_name
40
+
35
41
  self._manager: Optional[Manager] = None
36
42
 
37
43
  def __str__(self) -> str:
@@ -59,10 +65,14 @@ class Region(object):
59
65
  :class:`Region`
60
66
 
61
67
  """
68
+ id = obj.get('regionID', None)
69
+ region_name = obj.get('regionName', None)
70
+
62
71
  out = cls(
63
- id=obj['regionID'],
72
+ id=id,
64
73
  name=obj['region'],
65
74
  provider=obj['provider'],
75
+ region_name=region_name,
66
76
  )
67
77
  out._manager = manager
68
78
  return out
@@ -1074,7 +1074,7 @@ class WorkspaceGroup(object):
1074
1074
  try:
1075
1075
  region = [x for x in manager.regions if x.id == obj['regionID']][0]
1076
1076
  except IndexError:
1077
- region = Region(obj.get('regionID', '<unknown>'), '<unknown>', '<unknown>')
1077
+ region = Region('<unknown>', '<unknown>', obj.get('regionID', '<unknown>'))
1078
1078
  out = cls(
1079
1079
  name=obj['name'],
1080
1080
  id=obj['workspaceGroupID'],
@@ -1391,13 +1391,10 @@ class StarterWorkspace(object):
1391
1391
  msg='An endpoint has not been set in this '
1392
1392
  'starter workspace configuration',
1393
1393
  )
1394
- # Parse endpoint as host:port
1395
- if ':' in self.endpoint:
1396
- host, port = self.endpoint.split(':', 1)
1397
- kwargs['host'] = host
1398
- kwargs['port'] = int(port)
1399
- else:
1400
- kwargs['host'] = self.endpoint
1394
+
1395
+ kwargs['host'] = self.endpoint
1396
+ kwargs['database'] = self.database_name
1397
+
1401
1398
  return connection.connect(**kwargs)
1402
1399
 
1403
1400
  def terminate(self) -> None:
@@ -1455,7 +1452,7 @@ class StarterWorkspace(object):
1455
1452
 
1456
1453
  def create_user(
1457
1454
  self,
1458
- user_name: str,
1455
+ username: str,
1459
1456
  password: Optional[str] = None,
1460
1457
  ) -> Dict[str, str]:
1461
1458
  """
@@ -1463,7 +1460,7 @@ class StarterWorkspace(object):
1463
1460
 
1464
1461
  Parameters
1465
1462
  ----------
1466
- user_name : str
1463
+ username : str
1467
1464
  The starter workspace user name to connect the new user to the database
1468
1465
  password : str, optional
1469
1466
  Password for the new user. If not provided, a password will be
@@ -1485,7 +1482,7 @@ class StarterWorkspace(object):
1485
1482
  )
1486
1483
 
1487
1484
  payload = {
1488
- 'userName': user_name,
1485
+ 'userName': username,
1489
1486
  }
1490
1487
  if password is not None:
1491
1488
  payload['password'] = password
@@ -1715,7 +1712,7 @@ class WorkspaceManager(Manager):
1715
1712
  :class:`WorkspaceGroup`
1716
1713
 
1717
1714
  """
1718
- if isinstance(region, Region):
1715
+ if isinstance(region, Region) and region.id:
1719
1716
  region = region.id
1720
1717
  res = self._post(
1721
1718
  'workspaceGroups', json=dict(
@@ -1854,7 +1851,8 @@ class WorkspaceManager(Manager):
1854
1851
  self,
1855
1852
  name: str,
1856
1853
  database_name: str,
1857
- workspace_group: dict[str, str],
1854
+ provider: str,
1855
+ region_name: str,
1858
1856
  ) -> 'StarterWorkspace':
1859
1857
  """
1860
1858
  Create a new starter (shared tier) workspace.
@@ -1865,27 +1863,21 @@ class WorkspaceManager(Manager):
1865
1863
  Name of the starter workspace
1866
1864
  database_name : str
1867
1865
  Name of the database for the starter workspace
1868
- workspace_group : dict[str, str]
1869
- Workspace group input (dict with keys: 'cell_id')
1866
+ provider : str
1867
+ Cloud provider for the starter workspace (e.g., 'aws', 'gcp', 'azure')
1868
+ region_name : str
1869
+ Cloud provider region for the starter workspace (e.g., 'us-east-1')
1870
1870
 
1871
1871
  Returns
1872
1872
  -------
1873
1873
  :class:`StarterWorkspace`
1874
1874
  """
1875
- if not workspace_group or not isinstance(workspace_group, dict):
1876
- raise ValueError(
1877
- 'workspace_group must be a dict with keys: '
1878
- "'cell_id'",
1879
- )
1880
- if set(workspace_group.keys()) != {'cell_id'}:
1881
- raise ValueError("workspace_group must contain only 'cell_id'")
1882
1875
 
1883
1876
  payload = {
1884
1877
  'name': name,
1885
1878
  'databaseName': database_name,
1886
- 'workspaceGroup': {
1887
- 'cellID': workspace_group['cell_id'],
1888
- },
1879
+ 'provider': provider,
1880
+ 'regionName': region_name,
1889
1881
  }
1890
1882
 
1891
1883
  res = self._post('sharedtier/virtualWorkspaces', json=payload)
@@ -28,6 +28,45 @@ from singlestoredb.functions.typing import polars as plt
28
28
  from singlestoredb.functions.typing import pyarrow as pat
29
29
 
30
30
 
31
+ @udf
32
+ def doc_test(x: int, y: float) -> int:
33
+ """
34
+ A simple function to test the decorator and documentation.
35
+
36
+ Parameters
37
+ ----------
38
+ x : int
39
+ An integer to be multiplied by 2.
40
+ y : float
41
+ A float that is not used in the computation.
42
+
43
+ Examples
44
+ --------
45
+ Basic usage of the function:
46
+ >>> doc_test(
47
+ ... 3, 4.5,
48
+ ... )
49
+ 6
50
+
51
+ Another example with different values:
52
+ >>> doc_test(5, 2.0)
53
+ 10
54
+
55
+ SQL Example
56
+ sql> SELECT doc_test(3, 4.5);
57
+ 6
58
+
59
+ Final text
60
+
61
+ Returns
62
+ -------
63
+ int
64
+ The input integer multiplied by 2.
65
+
66
+ """
67
+ return x * 2
68
+
69
+
31
70
  @udf
32
71
  def int_mult(x: int, y: int) -> int:
33
72
  return x * y
@@ -1443,6 +1443,11 @@ class TestConnection(unittest.TestCase):
1443
1443
  out = cur.fetchone()
1444
1444
  row = dict(zip(names, out.row(0)))
1445
1445
 
1446
+ # Recent versions of polars have a problem with decimals
1447
+ class FixCompare(str):
1448
+ def __eq__(self, other):
1449
+ return super().__eq__(other.replace('precision=None', 'precision=22'))
1450
+
1446
1451
  dtypes = [
1447
1452
  ('id', 'Int32'),
1448
1453
  ('tinyint', 'Int8'),
@@ -1464,10 +1469,10 @@ class TestConnection(unittest.TestCase):
1464
1469
  ('float', 'Float32'),
1465
1470
  ('double', 'Float64'),
1466
1471
  ('real', 'Float64'),
1467
- ('decimal', 'Decimal(precision=22, scale=6)'),
1468
- ('dec', 'Decimal(precision=22, scale=6)'),
1469
- ('fixed', 'Decimal(precision=22, scale=6)'),
1470
- ('numeric', 'Decimal(precision=22, scale=6)'),
1472
+ ('decimal', FixCompare('Decimal(precision=22, scale=6)')),
1473
+ ('dec', FixCompare('Decimal(precision=22, scale=6)')),
1474
+ ('fixed', FixCompare('Decimal(precision=22, scale=6)')),
1475
+ ('numeric', FixCompare('Decimal(precision=22, scale=6)')),
1471
1476
  ('date', 'Date'),
1472
1477
  ('time', "Duration(time_unit='us')"),
1473
1478
  ('time_6', "Duration(time_unit='us')"),
@@ -1585,6 +1590,11 @@ class TestConnection(unittest.TestCase):
1585
1590
  out = cur.fetchone()
1586
1591
  row = dict(zip(names, out.row(0)))
1587
1592
 
1593
+ # Recent versions of polars have a problem with decimals
1594
+ class FixCompare(str):
1595
+ def __eq__(self, other):
1596
+ return super().__eq__(other.replace('precision=None', 'precision=22'))
1597
+
1588
1598
  dtypes = [
1589
1599
  ('id', 'Int32'),
1590
1600
  ('tinyint', 'Int8'),
@@ -1606,10 +1616,10 @@ class TestConnection(unittest.TestCase):
1606
1616
  ('float', 'Float32'),
1607
1617
  ('double', 'Float64'),
1608
1618
  ('real', 'Float64'),
1609
- ('decimal', 'Decimal(precision=22, scale=6)'),
1610
- ('dec', 'Decimal(precision=22, scale=6)'),
1611
- ('fixed', 'Decimal(precision=22, scale=6)'),
1612
- ('numeric', 'Decimal(precision=22, scale=6)'),
1619
+ ('decimal', FixCompare('Decimal(precision=22, scale=6)')),
1620
+ ('dec', FixCompare('Decimal(precision=22, scale=6)')),
1621
+ ('fixed', FixCompare('Decimal(precision=22, scale=6)')),
1622
+ ('numeric', FixCompare('Decimal(precision=22, scale=6)')),
1613
1623
  ('date', 'Date'),
1614
1624
  ('time', "Duration(time_unit='us')"),
1615
1625
  ('time_6', "Duration(time_unit='us')"),
@@ -14,7 +14,6 @@ import singlestoredb as s2
14
14
  from singlestoredb.management.job import Status
15
15
  from singlestoredb.management.job import TargetType
16
16
  from singlestoredb.management.region import Region
17
- from singlestoredb.management.region import RegionManager
18
17
  from singlestoredb.management.utils import NamedList
19
18
 
20
19
 
@@ -26,6 +25,11 @@ def clean_name(s):
26
25
  return re.sub(r'[^\w]', r'-', s).replace('_', '-').lower()
27
26
 
28
27
 
28
+ def shared_database_name(s):
29
+ """Return a shared database name. Cannot contain special characters except -"""
30
+ return re.sub(r'[^\w]', '', s).replace('-', '_').lower()
31
+
32
+
29
33
  @pytest.mark.management
30
34
  class TestCluster(unittest.TestCase):
31
35
 
@@ -366,42 +370,36 @@ class TestWorkspace(unittest.TestCase):
366
370
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
367
371
 
368
372
 
369
- @pytest.mark.skip('Not implemented in server yet')
370
373
  @pytest.mark.management
371
374
  class TestStarterWorkspace(unittest.TestCase):
372
375
 
373
376
  manager = None
374
377
  starter_workspace = None
375
- starter_workspace_user = {
376
- 'username': 'starter_user',
377
- 'password': None,
378
- }
379
-
380
- @property
381
- def starter_username(self):
382
- """Return the username for the starter workspace user."""
383
- return self.starter_workspace_user['username']
384
-
385
- @property
386
- def password(self):
387
- """Return the password for the starter workspace user."""
388
- return self.starter_workspace_user['password']
389
378
 
390
379
  @classmethod
391
380
  def setUpClass(cls):
392
381
  cls.manager = s2.manage_workspaces()
393
382
 
394
- us_regions = [x for x in cls.manager.regions if 'US' in x.name]
395
- cls.password = secrets.token_urlsafe(20) + '-x&$'
383
+ shared_tier_regions: NamedList[Region] = [
384
+ x for x in cls.manager.shared_tier_regions if 'US' in x.name
385
+ ]
386
+ cls.starter_username = 'starter_user'
387
+ cls.password = secrets.token_urlsafe(20)
396
388
 
397
- name = clean_name(secrets.token_urlsafe(20)[:20])
389
+ name = shared_database_name(secrets.token_urlsafe(20)[:20])
390
+
391
+ cls.database_name = f'starter_db_{name}'
392
+
393
+ shared_tier_region: Region = random.choice(shared_tier_regions)
394
+
395
+ if not shared_tier_region:
396
+ raise ValueError('No shared tier regions found')
398
397
 
399
398
  cls.starter_workspace = cls.manager.create_starter_workspace(
400
399
  f'starter-ws-test-{name}',
401
- database_name=f'starter_db_{name}',
402
- workspace_group={
403
- 'cell_id': random.choice(us_regions).id,
404
- },
400
+ database_name=cls.database_name,
401
+ provider=shared_tier_region.provider,
402
+ region_name=shared_tier_region.region_name,
405
403
  )
406
404
 
407
405
  cls.starter_workspace.create_user(
@@ -469,14 +467,14 @@ class TestStarterWorkspace(unittest.TestCase):
469
467
  ) as conn:
470
468
  with conn.cursor() as cur:
471
469
  cur.execute('show databases')
472
- assert 'starter_db' in [x[0] for x in list(cur)]
470
+ assert self.database_name in [x[0] for x in list(cur)]
473
471
 
474
472
  # Test missing endpoint
475
473
  workspace = self.manager.get_starter_workspace(self.starter_workspace.id)
476
474
  workspace.endpoint = None
477
475
 
478
476
  with self.assertRaises(s2.ManagementError) as cm:
479
- workspace.connect(user='admin', password=self.password)
477
+ workspace.connect(user=self.starter_username, password=self.password)
480
478
 
481
479
  assert 'endpoint' in cm.exception.msg, cm.exception.msg
482
480
 
@@ -1492,7 +1490,6 @@ class TestFileSpaces(unittest.TestCase):
1492
1490
  space.remove('obj_test_2.ipynb')
1493
1491
 
1494
1492
 
1495
- @pytest.mark.skip('Not implemented in server yet')
1496
1493
  @pytest.mark.management
1497
1494
  class TestRegions(unittest.TestCase):
1498
1495
  """Test cases for region management."""
@@ -1530,14 +1527,6 @@ class TestRegions(unittest.TestCase):
1530
1527
  providers = {x.provider for x in regions}
1531
1528
  assert 'Azure' in providers or 'GCP' in providers or 'AWS' in providers
1532
1529
 
1533
- # Verify region can be accessed by name or ID
1534
- region_by_name = regions[region.name]
1535
- region_by_id = regions[region.id]
1536
- assert region_by_name == region_by_id
1537
- assert region_by_name.id == region.id
1538
- assert region_by_name.name == region.name
1539
- assert region_by_name.provider == region.provider
1540
-
1541
1530
  def test_list_shared_tier_regions(self):
1542
1531
  """Test listing shared tier regions."""
1543
1532
  regions = self.manager.list_shared_tier_regions()
@@ -1549,22 +1538,14 @@ class TestRegions(unittest.TestCase):
1549
1538
  if regions:
1550
1539
  region = regions[0]
1551
1540
  assert isinstance(region, Region)
1552
- assert hasattr(region, 'id')
1553
1541
  assert hasattr(region, 'name')
1554
1542
  assert hasattr(region, 'provider')
1543
+ assert hasattr(region, 'region_name')
1555
1544
 
1556
1545
  # Verify provider values
1557
1546
  providers = {x.provider for x in regions}
1558
1547
  assert any(p in providers for p in ['Azure', 'GCP', 'AWS'])
1559
1548
 
1560
- # Verify region can be accessed by name or ID
1561
- region_by_name = regions[region.name]
1562
- region_by_id = regions[region.id]
1563
- assert region_by_name == region_by_id
1564
- assert region_by_name.id == region.id
1565
- assert region_by_name.name == region.name
1566
- assert region_by_name.provider == region.provider
1567
-
1568
1549
  def test_str_repr(self):
1569
1550
  """Test string representation of regions."""
1570
1551
  regions = self.manager.list_regions()
@@ -1581,17 +1562,3 @@ class TestRegions(unittest.TestCase):
1581
1562
 
1582
1563
  # Test __repr__
1583
1564
  assert repr(region) == str(region)
1584
-
1585
- def test_no_manager(self):
1586
- """Test behavior when manager is not available."""
1587
- regions = self.manager.list_regions()
1588
- if not regions:
1589
- self.skipTest('No regions available for testing')
1590
-
1591
- region = regions[0]
1592
- region._manager = None
1593
-
1594
- # Verify from_dict class method
1595
- with self.assertRaises(s2.ManagementError) as cm:
1596
- RegionManager.list_shared_tier_regions(None)
1597
- assert 'No workspace manager' in str(cm.exception)
@@ -36,6 +36,32 @@ def to_sql(x):
36
36
 
37
37
  class TestUDF(unittest.TestCase):
38
38
 
39
+ def test_invalid_signature(self):
40
+
41
+ def foo(x: np.ndarray, y: np.ndarray) -> str: ...
42
+ with self.assertRaises(TypeError):
43
+ to_sql(foo)
44
+
45
+ def foo(x: str, y: str) -> np.ndarray: ...
46
+ with self.assertRaises(TypeError):
47
+ to_sql(foo)
48
+
49
+ def foo(x: str, y: np.ndarray) -> np.ndarray: ...
50
+ with self.assertRaises(TypeError):
51
+ to_sql(foo)
52
+
53
+ def foo(x: np.ndarray, y: str) -> np.ndarray: ...
54
+ with self.assertRaises(TypeError):
55
+ to_sql(foo)
56
+
57
+ def foo(x: str, y: np.ndarray) -> str: ...
58
+ with self.assertRaises(TypeError):
59
+ to_sql(foo)
60
+
61
+ def foo(x: np.ndarray, y: str) -> str: ...
62
+ with self.assertRaises(TypeError):
63
+ to_sql(foo)
64
+
39
65
  def test_return_annotations(self):
40
66
 
41
67
  # No annotations
@@ -76,24 +102,25 @@ class TestUDF(unittest.TestCase):
76
102
  assert to_sql(foo) == '`foo`() RETURNS DOUBLE NULL'
77
103
 
78
104
  # Optional return value with collection type
79
- def foo() -> Optional[List[str]]: ...
80
- assert to_sql(foo) == '`foo`() RETURNS ARRAY(TEXT NOT NULL) NULL'
105
+ # def foo() -> Optional[List[str]]: ...
106
+ # assert to_sql(foo) == '`foo`() RETURNS ARRAY(TEXT NOT NULL) NULL'
81
107
 
82
108
  # Optional return value with nested collection type
83
- def foo() -> Optional[List[List[str]]]: ...
84
- assert to_sql(foo) == '`foo`() RETURNS ARRAY(ARRAY(TEXT NOT NULL) NOT NULL) NULL'
109
+ # def foo() -> Optional[List[List[str]]]: ...
110
+ # assert to_sql(foo) == '`foo`()
111
+ # RETURNS ARRAY(ARRAY(TEXT NOT NULL) NOT NULL) NULL'
85
112
 
86
113
  # Optional return value with collection type with nulls
87
- def foo() -> Optional[List[Optional[str]]]: ...
88
- assert to_sql(foo) == '`foo`() RETURNS ARRAY(TEXT NULL) NULL'
114
+ # def foo() -> Optional[List[Optional[str]]]: ...
115
+ # assert to_sql(foo) == '`foo`() RETURNS ARRAY(TEXT NULL) NULL'
89
116
 
90
117
  # Custom type with bound
91
118
  def foo() -> D: ...
92
119
  assert to_sql(foo) == '`foo`() RETURNS TEXT NOT NULL'
93
120
 
94
121
  # Return value with custom collection type with nulls
95
- def foo() -> E: ...
96
- assert to_sql(foo) == '`foo`() RETURNS ARRAY(DOUBLE NULL) NULL'
122
+ # def foo() -> E: ...
123
+ # assert to_sql(foo) == '`foo`() RETURNS ARRAY(DOUBLE NULL) NULL'
97
124
 
98
125
  # Incompatible types
99
126
  def foo() -> Union[int, str]: ...
@@ -158,17 +185,18 @@ class TestUDF(unittest.TestCase):
158
185
  assert to_sql(foo) == '`foo`(`x` DOUBLE NULL) RETURNS TINYINT NULL'
159
186
 
160
187
  # Optional parameter with collection type
161
- def foo(x: Optional[List[str]]) -> None: ...
162
- assert to_sql(foo) == '`foo`(`x` ARRAY(TEXT NOT NULL) NULL) RETURNS TINYINT NULL'
188
+ # def foo(x: Optional[List[str]]) -> None: ...
189
+ # assert to_sql(foo) == '`foo`(`x`
190
+ # ARRAY(TEXT NOT NULL) NULL) RETURNS TINYINT NULL'
163
191
 
164
192
  # Optional parameter with nested collection type
165
- def foo(x: Optional[List[List[str]]]) -> None: ...
166
- assert to_sql(foo) == '`foo`(`x` ARRAY(ARRAY(TEXT NOT NULL) NOT NULL) NULL) ' \
167
- 'RETURNS TINYINT NULL'
193
+ # def foo(x: Optional[List[List[str]]]) -> None: ...
194
+ # assert to_sql(foo) == '`foo`(`x` ARRAY(ARRAY(TEXT NOT NULL) NOT NULL) NULL) ' \
195
+ # 'RETURNS TINYINT NULL'
168
196
 
169
197
  # Optional parameter with collection type with nulls
170
- def foo(x: Optional[List[Optional[str]]]) -> None: ...
171
- assert to_sql(foo) == '`foo`(`x` ARRAY(TEXT NULL) NULL) RETURNS TINYINT NULL'
198
+ # def foo(x: Optional[List[Optional[str]]]) -> None: ...
199
+ # assert to_sql(foo) == '`foo`(`x` ARRAY(TEXT NULL) NULL) RETURNS TINYINT NULL'
172
200
 
173
201
  # Custom type with bound
174
202
  def foo(x: D) -> None: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.15.0
3
+ Version: 1.15.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