singlestoredb 1.14.2__py3-none-any.whl → 1.15.1__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.

Files changed (54) hide show
  1. singlestoredb/__init__.py +2 -2
  2. singlestoredb/ai/chat.py +14 -0
  3. singlestoredb/apps/_python_udfs.py +3 -3
  4. singlestoredb/config.py +11 -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 +51 -31
  25. singlestoredb/functions/ext/asgi.py +381 -35
  26. singlestoredb/functions/ext/timer.py +98 -0
  27. singlestoredb/functions/signature.py +374 -241
  28. singlestoredb/functions/typing/numpy.py +20 -0
  29. singlestoredb/functions/typing/pandas.py +2 -0
  30. singlestoredb/functions/typing/polars.py +2 -0
  31. singlestoredb/functions/typing/pyarrow.py +2 -0
  32. singlestoredb/fusion/handlers/files.py +4 -4
  33. singlestoredb/fusion/handlers/models.py +1 -1
  34. singlestoredb/fusion/handlers/stage.py +4 -4
  35. singlestoredb/magics/run_personal.py +82 -1
  36. singlestoredb/magics/run_shared.py +82 -1
  37. singlestoredb/management/__init__.py +1 -0
  38. singlestoredb/management/cluster.py +1 -1
  39. singlestoredb/management/manager.py +15 -5
  40. singlestoredb/management/region.py +104 -2
  41. singlestoredb/management/workspace.py +174 -3
  42. singlestoredb/tests/ext_funcs/__init__.py +133 -55
  43. singlestoredb/tests/test.sql +22 -0
  44. singlestoredb/tests/test_connection.py +18 -8
  45. singlestoredb/tests/test_ext_func.py +90 -0
  46. singlestoredb/tests/test_management.py +190 -0
  47. singlestoredb/tests/test_udf.py +43 -15
  48. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/METADATA +1 -1
  49. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/RECORD +54 -30
  50. /singlestoredb/functions/{typing.py → typing/__init__.py} +0 -0
  51. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/LICENSE +0 -0
  52. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/WHEEL +0 -0
  53. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/entry_points.txt +0 -0
  54. {singlestoredb-1.14.2.dist-info → singlestoredb-1.15.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,20 @@
1
+ import numpy as np
2
+ import numpy.typing as npt
3
+
4
+ NDArray = npt.NDArray
5
+
6
+ StringArray = StrArray = npt.NDArray[np.str_]
7
+ BytesArray = npt.NDArray[np.bytes_]
8
+ Float32Array = FloatArray = npt.NDArray[np.float32]
9
+ Float64Array = DoubleArray = npt.NDArray[np.float64]
10
+ IntArray = npt.NDArray[np.int_]
11
+ Int8Array = npt.NDArray[np.int8]
12
+ Int16Array = npt.NDArray[np.int16]
13
+ Int32Array = npt.NDArray[np.int32]
14
+ Int64Array = npt.NDArray[np.int64]
15
+ UInt8Array = npt.NDArray[np.uint8]
16
+ UInt16Array = npt.NDArray[np.uint16]
17
+ UInt32Array = npt.NDArray[np.uint32]
18
+ UInt64Array = npt.NDArray[np.uint64]
19
+ DateTimeArray = npt.NDArray[np.datetime64]
20
+ TimeDeltaArray = npt.NDArray[np.timedelta64]
@@ -0,0 +1,2 @@
1
+ from pandas import DataFrame # noqa: F401
2
+ from pandas import Series # noqa: F401
@@ -0,0 +1,2 @@
1
+ from polars import DataFrame # noqa: F401
2
+ from polars import Series # noqa: F401
@@ -0,0 +1,2 @@
1
+ from pyarrow import Array # noqa: F401
2
+ from pyarrow import Table # noqa: F401
@@ -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
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import tempfile
3
+ from pathlib import Path
3
4
  from typing import Any
5
+ from warnings import warn
4
6
 
5
7
  from IPython.core.interactiveshell import InteractiveShell
6
8
  from IPython.core.magic import line_magic
@@ -8,6 +10,8 @@ from IPython.core.magic import Magics
8
10
  from IPython.core.magic import magics_class
9
11
  from IPython.core.magic import needs_local_scope
10
12
  from IPython.core.magic import no_var_expand
13
+ from IPython.utils.contexts import preserve_keys
14
+ from IPython.utils.syspathcontext import prepended_to_syspath
11
15
  from jinja2 import Template
12
16
 
13
17
 
@@ -53,4 +57,81 @@ class RunPersonalMagic(Magics):
53
57
  # Execute the SQL command
54
58
  self.shell.run_line_magic('sql', sql_command)
55
59
  # Run the downloaded file
56
- self.shell.run_line_magic('run', f'"{temp_file_path}"')
60
+ with preserve_keys(self.shell.user_ns, '__file__'):
61
+ self.shell.user_ns['__file__'] = temp_file_path
62
+ self.safe_execfile_ipy(temp_file_path, raise_exceptions=True)
63
+
64
+ def safe_execfile_ipy(
65
+ self,
66
+ fname: str,
67
+ shell_futures: bool = False,
68
+ raise_exceptions: bool = False,
69
+ ) -> None:
70
+ """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
71
+
72
+ Parameters
73
+ ----------
74
+ fname : str
75
+ The name of the file to execute. The filename must have a
76
+ .ipy or .ipynb extension.
77
+ shell_futures : bool (False)
78
+ If True, the code will share future statements with the interactive
79
+ shell. It will both be affected by previous __future__ imports, and
80
+ any __future__ imports in the code will affect the shell. If False,
81
+ __future__ imports are not shared in either direction.
82
+ raise_exceptions : bool (False)
83
+ If True raise exceptions everywhere. Meant for testing.
84
+ """
85
+ fpath = Path(fname).expanduser().resolve()
86
+
87
+ # Make sure we can open the file
88
+ try:
89
+ with fpath.open('rb'):
90
+ pass
91
+ except Exception:
92
+ warn('Could not open file <%s> for safe execution.' % fpath)
93
+ return
94
+
95
+ # Find things also in current directory. This is needed to mimic the
96
+ # behavior of running a script from the system command line, where
97
+ # Python inserts the script's directory into sys.path
98
+ dname = str(fpath.parent)
99
+
100
+ def get_cells() -> Any:
101
+ """generator for sequence of code blocks to run"""
102
+ if fpath.suffix == '.ipynb':
103
+ from nbformat import read
104
+ nb = read(fpath, as_version=4)
105
+ if not nb.cells:
106
+ return
107
+ for cell in nb.cells:
108
+ if cell.cell_type == 'code':
109
+ if not cell.source.strip():
110
+ continue
111
+ if getattr(cell, 'metadata', {}).get('language', '') == 'sql':
112
+ output_redirect = getattr(
113
+ cell, 'metadata', {},
114
+ ).get('output_variable', '') or ''
115
+ if output_redirect:
116
+ output_redirect = f' {output_redirect} <<'
117
+ yield f'%%sql{output_redirect}\n{cell.source}'
118
+ else:
119
+ yield cell.source
120
+ else:
121
+ yield fpath.read_text(encoding='utf-8')
122
+
123
+ with prepended_to_syspath(dname):
124
+ try:
125
+ for cell in get_cells():
126
+ result = self.shell.run_cell(
127
+ cell, silent=True, shell_futures=shell_futures,
128
+ )
129
+ if raise_exceptions:
130
+ result.raise_error()
131
+ elif not result.success:
132
+ break
133
+ except Exception:
134
+ if raise_exceptions:
135
+ raise
136
+ self.shell.showtraceback()
137
+ warn('Unknown failure executing file: <%s>' % fpath)
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import tempfile
3
+ from pathlib import Path
3
4
  from typing import Any
5
+ from warnings import warn
4
6
 
5
7
  from IPython.core.interactiveshell import InteractiveShell
6
8
  from IPython.core.magic import line_magic
@@ -8,6 +10,8 @@ from IPython.core.magic import Magics
8
10
  from IPython.core.magic import magics_class
9
11
  from IPython.core.magic import needs_local_scope
10
12
  from IPython.core.magic import no_var_expand
13
+ from IPython.utils.contexts import preserve_keys
14
+ from IPython.utils.syspathcontext import prepended_to_syspath
11
15
  from jinja2 import Template
12
16
 
13
17
 
@@ -50,4 +54,81 @@ class RunSharedMagic(Magics):
50
54
  # Execute the SQL command
51
55
  self.shell.run_line_magic('sql', sql_command)
52
56
  # Run the downloaded file
53
- self.shell.run_line_magic('run', f'"{temp_file_path}"')
57
+ with preserve_keys(self.shell.user_ns, '__file__'):
58
+ self.shell.user_ns['__file__'] = temp_file_path
59
+ self.safe_execfile_ipy(temp_file_path, raise_exceptions=True)
60
+
61
+ def safe_execfile_ipy(
62
+ self,
63
+ fname: str,
64
+ shell_futures: bool = False,
65
+ raise_exceptions: bool = False,
66
+ ) -> None:
67
+ """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
68
+
69
+ Parameters
70
+ ----------
71
+ fname : str
72
+ The name of the file to execute. The filename must have a
73
+ .ipy or .ipynb extension.
74
+ shell_futures : bool (False)
75
+ If True, the code will share future statements with the interactive
76
+ shell. It will both be affected by previous __future__ imports, and
77
+ any __future__ imports in the code will affect the shell. If False,
78
+ __future__ imports are not shared in either direction.
79
+ raise_exceptions : bool (False)
80
+ If True raise exceptions everywhere. Meant for testing.
81
+ """
82
+ fpath = Path(fname).expanduser().resolve()
83
+
84
+ # Make sure we can open the file
85
+ try:
86
+ with fpath.open('rb'):
87
+ pass
88
+ except Exception:
89
+ warn('Could not open file <%s> for safe execution.' % fpath)
90
+ return
91
+
92
+ # Find things also in current directory. This is needed to mimic the
93
+ # behavior of running a script from the system command line, where
94
+ # Python inserts the script's directory into sys.path
95
+ dname = str(fpath.parent)
96
+
97
+ def get_cells() -> Any:
98
+ """generator for sequence of code blocks to run"""
99
+ if fpath.suffix == '.ipynb':
100
+ from nbformat import read
101
+ nb = read(fpath, as_version=4)
102
+ if not nb.cells:
103
+ return
104
+ for cell in nb.cells:
105
+ if cell.cell_type == 'code':
106
+ if not cell.source.strip():
107
+ continue
108
+ if getattr(cell, 'metadata', {}).get('language', '') == 'sql':
109
+ output_redirect = getattr(
110
+ cell, 'metadata', {},
111
+ ).get('output_variable', '') or ''
112
+ if output_redirect:
113
+ output_redirect = f' {output_redirect} <<'
114
+ yield f'%%sql{output_redirect}\n{cell.source}'
115
+ else:
116
+ yield cell.source
117
+ else:
118
+ yield fpath.read_text(encoding='utf-8')
119
+
120
+ with prepended_to_syspath(dname):
121
+ try:
122
+ for cell in get_cells():
123
+ result = self.shell.run_cell(
124
+ cell, silent=True, shell_futures=shell_futures,
125
+ )
126
+ if raise_exceptions:
127
+ result.raise_error()
128
+ elif not result.success:
129
+ break
130
+ except Exception:
131
+ if raise_exceptions:
132
+ raise
133
+ self.shell.showtraceback()
134
+ warn('Unknown failure executing file: <%s>' % fpath)
@@ -2,6 +2,7 @@
2
2
  from .cluster import manage_cluster
3
3
  from .files import manage_files
4
4
  from .manager import get_token
5
+ from .region import manage_regions
5
6
  from .workspace import get_organization
6
7
  from .workspace import get_secret
7
8
  from .workspace import get_stage
@@ -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
 
@@ -4,6 +4,7 @@ from typing import Dict
4
4
  from typing import Optional
5
5
 
6
6
  from .manager import Manager
7
+ from .utils import NamedList
7
8
  from .utils import vars_to_str
8
9
 
9
10
 
@@ -20,7 +21,10 @@ class Region(object):
20
21
 
21
22
  """
22
23
 
23
- 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:
24
28
  """Use :attr:`WorkspaceManager.regions` instead."""
25
29
  #: Unique ID of the region
26
30
  self.id = id
@@ -31,6 +35,9 @@ class Region(object):
31
35
  #: Name of the cloud provider
32
36
  self.provider = provider
33
37
 
38
+ #: Name of the provider region
39
+ self.region_name = region_name
40
+
34
41
  self._manager: Optional[Manager] = None
35
42
 
36
43
  def __str__(self) -> str:
@@ -58,10 +65,105 @@ class Region(object):
58
65
  :class:`Region`
59
66
 
60
67
  """
68
+ id = obj.get('regionID', None)
69
+ region_name = obj.get('regionName', None)
70
+
61
71
  out = cls(
62
- id=obj['regionID'],
72
+ id=id,
63
73
  name=obj['region'],
64
74
  provider=obj['provider'],
75
+ region_name=region_name,
65
76
  )
66
77
  out._manager = manager
67
78
  return out
79
+
80
+
81
+ class RegionManager(Manager):
82
+ """
83
+ SingleStoreDB region manager.
84
+
85
+ This class should be instantiated using :func:`singlestoredb.manage_regions`.
86
+
87
+ Parameters
88
+ ----------
89
+ access_token : str, optional
90
+ The API key or other access token for the workspace management API
91
+ version : str, optional
92
+ Version of the API to use
93
+ base_url : str, optional
94
+ Base URL of the workspace management API
95
+
96
+ See Also
97
+ --------
98
+ :func:`singlestoredb.manage_regions`
99
+ """
100
+
101
+ #: Object type
102
+ obj_type = 'region'
103
+
104
+ def list_regions(self) -> NamedList[Region]:
105
+ """
106
+ List all available regions.
107
+
108
+ Returns
109
+ -------
110
+ NamedList[Region]
111
+ List of available regions
112
+
113
+ Raises
114
+ ------
115
+ ManagementError
116
+ If there is an error getting the regions
117
+ """
118
+ res = self._get('regions')
119
+ return NamedList(
120
+ [Region.from_dict(item, self) for item in res.json()],
121
+ )
122
+
123
+ def list_shared_tier_regions(self) -> NamedList[Region]:
124
+ """
125
+ List regions that support shared tier workspaces.
126
+
127
+ Returns
128
+ -------
129
+ NamedList[Region]
130
+ List of regions that support shared tier workspaces
131
+
132
+ Raises
133
+ ------
134
+ ManagementError
135
+ If there is an error getting the regions
136
+ """
137
+ res = self._get('regions/sharedtier')
138
+ return NamedList(
139
+ [Region.from_dict(item, self) for item in res.json()],
140
+ )
141
+
142
+
143
+ def manage_regions(
144
+ access_token: Optional[str] = None,
145
+ version: Optional[str] = None,
146
+ base_url: Optional[str] = None,
147
+ ) -> RegionManager:
148
+ """
149
+ Retrieve a SingleStoreDB region manager.
150
+
151
+ Parameters
152
+ ----------
153
+ access_token : str, optional
154
+ The API key or other access token for the workspace management API
155
+ version : str, optional
156
+ Version of the API to use
157
+ base_url : str, optional
158
+ Base URL of the workspace management API
159
+
160
+ Returns
161
+ -------
162
+ :class:`RegionManager`
163
+
164
+ """
165
+ return RegionManager(
166
+ access_token=access_token,
167
+ version=version,
168
+ base_url=base_url,
169
+ )