singlestoredb 1.9.0__cp38-abi3-win32.whl → 1.11.0__cp38-abi3-win32.whl

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

Potentially problematic release.


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

_singlestoredb_accel.pyd CHANGED
Binary file
singlestoredb/__init__.py CHANGED
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.9.0'
16
+ __version__ = '1.11.0'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -641,13 +641,6 @@ class SQLHandler(NodeVisitor):
641
641
  DummySQLResult
642
642
 
643
643
  """
644
- import warnings
645
- warnings.warn(
646
- 'Fusion SQL is currently a preview feature. '
647
- 'Run `SHOW FUSION COMMANDS` to see all commands.',
648
- RuntimeWarning,
649
- )
650
-
651
644
  type(self).compile()
652
645
  self._handled = set()
653
646
  try:
@@ -55,10 +55,10 @@ class CreateClusterIdentity(SQLHandler):
55
55
 
56
56
  CREATE CLUSTER IDENTITY
57
57
  CATALOG CONFIG '{
58
- "type": "GLUE",
58
+ "catalog_type": "GLUE",
59
59
  "table_format": "ICEBERG",
60
- "id": "13983498723498",
61
- "region": "us-east-1"
60
+ "catalog_id": "13983498723498",
61
+ "catalog_region": "us-east-1"
62
62
  }'
63
63
  LINK S3 CONFIG '{
64
64
  "region": "us-east-1",
@@ -78,6 +78,8 @@ class CreateClusterIdentity(SQLHandler):
78
78
  storage_config = json.loads(params['storage'].get('link_config', '{}') or '{}')
79
79
  storage_creds = json.loads(params['storage'].get('link_creds', '{}') or '{}')
80
80
 
81
+ storage_config['provider'] = 'S3'
82
+
81
83
  wsg = get_workspace_group({})
82
84
 
83
85
  if wsg._manager is None:
@@ -145,12 +147,12 @@ class CreateExport(SQLHandler):
145
147
  catalog and link configurations. The source table to export is
146
148
  named "customer_data"::
147
149
 
148
- START EXPORT FROM customer_data
150
+ START EXPORT FROM my_db.customer_data
149
151
  CATALOG CONFIG '{
150
- "type": "GLUE",
152
+ "catalog_type": "GLUE",
151
153
  "table_format": "ICEBERG",
152
- "id": "13983498723498",
153
- "region": "us-east-1"
154
+ "catalog_id": "13983498723498",
155
+ "catalog_region": "us-east-1"
154
156
  }'
155
157
  LINK S3 CONFIG '{
156
158
  "region": "us-east-1",
@@ -177,6 +179,8 @@ class CreateExport(SQLHandler):
177
179
  storage_config = json.loads(params['storage'].get('link_config', '{}') or '{}')
178
180
  storage_creds = json.loads(params['storage'].get('link_creds', '{}') or '{}')
179
181
 
182
+ storage_config['provider'] = 'S3'
183
+
180
184
  wsg = get_workspace_group({})
181
185
 
182
186
  if from_database is None:
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ from typing import Any
4
+ from typing import Dict
5
+ from typing import Optional
6
+
7
+ from ..handler import SQLHandler
8
+ from ..result import FusionSQLResult
9
+ from .files import ShowFilesHandler
10
+ from .utils import get_file_space
11
+
12
+
13
+ class ShowModelsHandler(ShowFilesHandler):
14
+ """
15
+ SHOW MODELS
16
+ [ at_path ] [ <like> ]
17
+ [ <order-by> ]
18
+ [ <limit> ] [ recursive ] [ extended ];
19
+
20
+ # File path to list
21
+ at_path = AT '<path>'
22
+
23
+ # Should the listing be recursive?
24
+ recursive = RECURSIVE
25
+
26
+ # Should extended attributes be shown?
27
+ extended = EXTENDED
28
+
29
+ Description
30
+ -----------
31
+ Displays the list of models in models space.
32
+
33
+ Arguments
34
+ ---------
35
+ * ``<path>``: A path in the models space.
36
+ * ``<pattern>``: A pattern similar to SQL LIKE clause.
37
+ Uses ``%`` as the wildcard character.
38
+
39
+ Remarks
40
+ -------
41
+ * Use the ``LIKE`` clause to specify a pattern and return only the
42
+ files that match the specified pattern.
43
+ * The ``LIMIT`` clause limits the number of results to the
44
+ specified number.
45
+ * Use the ``ORDER BY`` clause to sort the results by the specified
46
+ key. By default, the results are sorted in the ascending order.
47
+ * The ``AT PATH`` clause specifies the path in the models
48
+ space to list the files from.
49
+ * To return more information about the files, use the ``EXTENDED``
50
+ clause.
51
+
52
+ Examples
53
+ --------
54
+ The following command lists the models::
55
+
56
+ SHOW MODELS;
57
+
58
+ The following command lists the models with additional information::
59
+
60
+ SHOW MODELS EXTENDED;
61
+
62
+ See Also
63
+ --------
64
+ * ``UPLOAD MODEL model_name FROM path``
65
+ * ``DOWNLOAD MODEL model_name``
66
+
67
+
68
+ """ # noqa: E501
69
+
70
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
71
+ params['file_location'] = 'MODELS'
72
+
73
+ return super().run(params)
74
+
75
+
76
+ ShowModelsHandler.register(overwrite=True)
77
+
78
+
79
+ class UploadModelHandler(SQLHandler):
80
+ """
81
+ UPLOAD MODEL model_name
82
+ FROM local_path [ overwrite ];
83
+
84
+ # Model Name
85
+ model_name = '<model-name>'
86
+
87
+ # Path to local file or directory
88
+ local_path = '<local-path>'
89
+
90
+ # Should an existing file be overwritten?
91
+ overwrite = OVERWRITE
92
+
93
+ Description
94
+ -----------
95
+ Uploads a file or folder to models space.
96
+
97
+ Arguments
98
+ ---------
99
+ * ``<model-name>``: Model name.
100
+ * ``<local-path>``: The path to the file or folder to upload in the local
101
+ directory.
102
+
103
+ Remarks
104
+ -------
105
+ * If the ``OVERWRITE`` clause is specified, any existing file at the
106
+ specified path in the models space is overwritten.
107
+
108
+ Examples
109
+ --------
110
+ The following command uploads a file to models space and overwrite any
111
+ existing files at the specified path::
112
+
113
+ UPLOAD MODEL model_name
114
+ FROM 'llama3/' OVERWRITE;
115
+
116
+ See Also
117
+ --------
118
+ * ``DOWNLOAD MODEL model_name``
119
+
120
+ """ # noqa: E501
121
+
122
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
123
+ params['file_location'] = 'MODELS'
124
+
125
+ model_name = params['model_name']
126
+ local_path = params['local_path']
127
+
128
+ file_space = get_file_space(params)
129
+
130
+ if os.path.isdir(local_path):
131
+ file_space.upload_folder(
132
+ local_path=local_path,
133
+ path=os.path.join(model_name, ''),
134
+ overwrite=params['overwrite'],
135
+ )
136
+ else:
137
+ file_space.upload_file(
138
+ local_path=local_path,
139
+ path=os.path.join(model_name, local_path),
140
+ overwrite=params['overwrite'],
141
+ )
142
+
143
+ return None
144
+
145
+
146
+ UploadModelHandler.register(overwrite=True)
147
+
148
+
149
+ class DownloadModelHandler(SQLHandler):
150
+ """
151
+ DOWNLOAD MODEL model_name
152
+ [ local_path ]
153
+ [ overwrite ];
154
+
155
+ # Model Name
156
+ model_name = '<model-name>'
157
+
158
+ # Path to local directory
159
+ local_path = TO '<local-path>'
160
+
161
+ # Should an existing directory be overwritten?
162
+ overwrite = OVERWRITE
163
+
164
+ Description
165
+ -----------
166
+ Download a model from models space.
167
+
168
+ Arguments
169
+ ---------
170
+ * ``<model-name>``: Model name to download in models space.
171
+ * ``<local-path>``: Specifies the path in the local directory
172
+ where the model is downloaded.
173
+
174
+ Remarks
175
+ -------
176
+ * If the ``OVERWRITE`` clause is specified, any existing file or folder at
177
+ the download location is overwritten.
178
+ * If ``<local-path>`` is not specified, the model is downloaded to the current location.
179
+
180
+ Examples
181
+ --------
182
+ The following command displays the contents of the file on the
183
+ standard output::
184
+
185
+ DOWNLOAD MODEL llama3;
186
+
187
+ The following command downloads a model to a specific location and
188
+ overwrites any existing models folder with the name ``local_llama3`` on the local storage::
189
+
190
+ DOWNLOAD MODEL llama3
191
+ TO 'local_llama3' OVERWRITE;
192
+
193
+ See Also
194
+ --------
195
+ * ``UPLOAD MODEL model_name FROM local_path``
196
+
197
+ """ # noqa: E501
198
+
199
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
200
+ params['file_location'] = 'MODELS'
201
+
202
+ file_space = get_file_space(params)
203
+
204
+ model_name = params['model_name']
205
+ file_space.download_folder(
206
+ path=os.path.join(model_name, ''),
207
+ local_path=params['local_path'] or model_name,
208
+ overwrite=params['overwrite'],
209
+ )
210
+
211
+ return None
212
+
213
+
214
+ DownloadModelHandler.register(overwrite=True)
215
+
216
+
217
+ class DropModelsHandler(SQLHandler):
218
+ """
219
+ DROP MODEL model_name;
220
+
221
+ # Model Name
222
+ model_name = '<model-name>'
223
+
224
+ Description
225
+ -----------
226
+ Deletes a model from models space.
227
+
228
+ Arguments
229
+ ---------
230
+ * ``<model-name>``: Model name to delete in models space.
231
+
232
+ Example
233
+ --------
234
+ The following commands deletes a model from a model space::
235
+
236
+ DROP MODEL llama3;
237
+
238
+ """ # noqa: E501
239
+
240
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
241
+ params['file_location'] = 'MODELS'
242
+ path = os.path.join(params['model_name'], '')
243
+
244
+ file_space = get_file_space(params)
245
+ file_space.removedirs(path=path)
246
+
247
+ return None
248
+
249
+
250
+ DropModelsHandler.register(overwrite=True)
@@ -11,6 +11,7 @@ from ...management import manage_workspaces
11
11
  from ...management.files import FilesManager
12
12
  from ...management.files import FileSpace
13
13
  from ...management.files import manage_files
14
+ from ...management.files import MODELS_SPACE
14
15
  from ...management.files import PERSONAL_SPACE
15
16
  from ...management.files import SHARED_SPACE
16
17
  from ...management.workspace import StarterWorkspace
@@ -296,15 +297,14 @@ def get_file_space(params: Dict[str, Any]) -> FileSpace:
296
297
  file_location = params.get('file_location')
297
298
  if file_location:
298
299
  file_location_lower_case = file_location.lower()
299
- if (
300
- file_location_lower_case != PERSONAL_SPACE and
301
- file_location_lower_case != SHARED_SPACE
302
- ):
303
- raise ValueError(f'invalid file location: {file_location}')
304
300
 
305
301
  if file_location_lower_case == PERSONAL_SPACE:
306
302
  return manager.personal_space
307
303
  elif file_location_lower_case == SHARED_SPACE:
308
304
  return manager.shared_space
305
+ elif file_location_lower_case == MODELS_SPACE:
306
+ return manager.models_space
307
+ else:
308
+ raise ValueError(f'invalid file location: {file_location}')
309
309
 
310
310
  raise KeyError('no file space was specified')
@@ -0,0 +1,34 @@
1
+ from IPython.core.interactiveshell import InteractiveShell
2
+
3
+ from .run_personal import RunPersonalMagic
4
+ from .run_shared import RunSharedMagic
5
+
6
+ # In order to actually use these magics, we must register them with a
7
+ # running IPython.
8
+
9
+
10
+ def load_ipython_extension(ip: InteractiveShell) -> None:
11
+ """
12
+ Any module file that define a function named `load_ipython_extension`
13
+ can be loaded via `%load_ext module.path` or be configured to be
14
+ autoloaded by IPython at startup time.
15
+ """
16
+
17
+ # Load jupysql extension
18
+ # This is necessary for jupysql to initialize internal state
19
+ # required to render messages
20
+ assert ip.extension_manager is not None
21
+ result = ip.extension_manager.load_extension('sql')
22
+ if result == 'no load function':
23
+ raise RuntimeError('Could not load sql extension. Is jupysql installed?')
24
+
25
+ # Check if %run magic command is defined
26
+ if ip.find_line_magic('run') is None:
27
+ raise RuntimeError(
28
+ '%run magic command is not defined. '
29
+ 'Is it available in your IPython environment?',
30
+ )
31
+
32
+ # Register run_personal and run_shared
33
+ ip.register_magics(RunPersonalMagic(ip))
34
+ ip.register_magics(RunSharedMagic(ip))
@@ -0,0 +1,56 @@
1
+ import os
2
+ import tempfile
3
+ from typing import Any
4
+
5
+ from IPython.core.interactiveshell import InteractiveShell
6
+ from IPython.core.magic import line_magic
7
+ from IPython.core.magic import Magics
8
+ from IPython.core.magic import magics_class
9
+ from IPython.core.magic import needs_local_scope
10
+ from IPython.core.magic import no_var_expand
11
+ from jinja2 import Template
12
+
13
+
14
+ @magics_class
15
+ class RunPersonalMagic(Magics):
16
+ def __init__(self, shell: InteractiveShell):
17
+ Magics.__init__(self, shell=shell)
18
+
19
+ @no_var_expand
20
+ @needs_local_scope
21
+ @line_magic('run_personal')
22
+ def run_personal(self, line: str, local_ns: Any = None) -> Any:
23
+ """
24
+ Downloads a personal file using the %sql magic and then runs it using %run.
25
+
26
+ Examples::
27
+
28
+ # Line usage
29
+
30
+ %run_personal personal_file.ipynb
31
+
32
+ %run_personal {{ sample_notebook_name }}
33
+
34
+ """
35
+
36
+ template = Template(line.strip())
37
+ personal_file = template.render(local_ns)
38
+ if not personal_file:
39
+ raise ValueError('No personal file specified.')
40
+ if (personal_file.startswith("'") and personal_file.endswith("'")) or \
41
+ (personal_file.startswith('"') and personal_file.endswith('"')):
42
+ personal_file = personal_file[1:-1]
43
+ if not personal_file:
44
+ raise ValueError('No personal file specified.')
45
+
46
+ with tempfile.TemporaryDirectory() as temp_dir:
47
+ temp_file_path = os.path.join(temp_dir, personal_file)
48
+ sql_command = (
49
+ f"DOWNLOAD PERSONAL FILE '{personal_file}' "
50
+ f"TO '{temp_file_path}'"
51
+ )
52
+
53
+ # Execute the SQL command
54
+ self.shell.run_line_magic('sql', sql_command)
55
+ # Run the downloaded file
56
+ self.shell.run_line_magic('run', f'"{temp_file_path}"')
@@ -0,0 +1,53 @@
1
+ import os
2
+ import tempfile
3
+ from typing import Any
4
+
5
+ from IPython.core.interactiveshell import InteractiveShell
6
+ from IPython.core.magic import line_magic
7
+ from IPython.core.magic import Magics
8
+ from IPython.core.magic import magics_class
9
+ from IPython.core.magic import needs_local_scope
10
+ from IPython.core.magic import no_var_expand
11
+ from jinja2 import Template
12
+
13
+
14
+ @magics_class
15
+ class RunSharedMagic(Magics):
16
+ def __init__(self, shell: InteractiveShell):
17
+ Magics.__init__(self, shell=shell)
18
+
19
+ @no_var_expand
20
+ @needs_local_scope
21
+ @line_magic('run_shared')
22
+ def run_shared(self, line: str, local_ns: Any = None) -> Any:
23
+ """
24
+ Downloads a shared file using the %sql magic and then runs it using %run.
25
+
26
+ Examples::
27
+
28
+ # Line usage
29
+
30
+ %run_shared shared_file.ipynb
31
+
32
+ %run_shared {{ sample_notebook_name }}
33
+
34
+ """
35
+
36
+ template = Template(line.strip())
37
+ shared_file = template.render(local_ns)
38
+ if not shared_file:
39
+ raise ValueError('No shared file specified.')
40
+ if (shared_file.startswith("'") and shared_file.endswith("'")) or \
41
+ (shared_file.startswith('"') and shared_file.endswith('"')):
42
+ shared_file = shared_file[1:-1]
43
+ if not shared_file:
44
+ raise ValueError('No personal file specified.')
45
+
46
+ with tempfile.TemporaryDirectory() as temp_dir:
47
+ temp_file_path = os.path.join(temp_dir, shared_file)
48
+ sql_command = f"DOWNLOAD SHARED FILE '{shared_file}' TO '{temp_file_path}'"
49
+
50
+ # Execute the SQL command
51
+ self.shell.run_line_magic('sql', sql_command)
52
+ # Run the downloaded file
53
+ self.shell.run_line_magic('run', f'"{temp_file_path}"')
@@ -333,7 +333,8 @@ class ClusterManager(Manager):
333
333
  default_version = 'v0beta'
334
334
 
335
335
  #: Base URL if none is specified.
336
- default_base_url = config.get_option('management.base_url')
336
+ default_base_url = config.get_option('management.base_url') \
337
+ or 'https://api.singlestore.com'
337
338
 
338
339
  #: Object type
339
340
  obj_type = 'cluster'
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import datetime
6
+ import glob
6
7
  import io
7
8
  import os
8
9
  import re
@@ -23,9 +24,9 @@ from .utils import PathLike
23
24
  from .utils import to_datetime
24
25
  from .utils import vars_to_str
25
26
 
26
-
27
27
  PERSONAL_SPACE = 'personal'
28
28
  SHARED_SPACE = 'shared'
29
+ MODELS_SPACE = 'models'
29
30
 
30
31
 
31
32
  class FilesObject(object):
@@ -35,8 +36,8 @@ class FilesObject(object):
35
36
  It can belong to either a workspace stage or personal/shared space.
36
37
 
37
38
  This object is not instantiated directly. It is used in the results
38
- of various operations in ``WorkspaceGroup.stage``, ``FilesManager.personal_space``
39
- and ``FilesManager.shared_space`` methods.
39
+ of various operations in ``WorkspaceGroup.stage``, ``FilesManager.personal_space``,
40
+ ``FilesManager.shared_space`` and ``FilesManager.models_space`` methods.
40
41
 
41
42
  """
42
43
 
@@ -494,10 +495,11 @@ class FilesManager(Manager):
494
495
  """
495
496
 
496
497
  #: Management API version if none is specified.
497
- default_version = config.get_option('management.version')
498
+ default_version = config.get_option('management.version') or 'v1'
498
499
 
499
500
  #: Base URL if none is specified.
500
- default_base_url = config.get_option('management.base_url')
501
+ default_base_url = config.get_option('management.base_url') \
502
+ or 'https://api.singlestore.com'
501
503
 
502
504
  #: Object type
503
505
  obj_type = 'file'
@@ -512,6 +514,11 @@ class FilesManager(Manager):
512
514
  """Return the shared file space."""
513
515
  return FileSpace(SHARED_SPACE, self)
514
516
 
517
+ @property
518
+ def models_space(self) -> FileSpace:
519
+ """Return the models file space."""
520
+ return FileSpace(MODELS_SPACE, self)
521
+
515
522
 
516
523
  def manage_files(
517
524
  access_token: Optional[str] = None,
@@ -550,7 +557,8 @@ class FileSpace(FileLocation):
550
557
  FileSpace manager.
551
558
 
552
559
  This object is not instantiated directly.
553
- It is returned by ``FilesManager.personal_space`` or ``FilesManager.shared_space``.
560
+ It is returned by ``FilesManager.personal_space``, ``FilesManager.shared_space``
561
+ or ``FileManger.models_space``.
554
562
 
555
563
  """
556
564
 
@@ -686,10 +694,36 @@ class FileSpace(FileLocation):
686
694
  ignore all '*.pyc' files in the directory tree
687
695
 
688
696
  """
689
- raise ManagementError(
690
- msg='Operation not supported: directories are currently not allowed '
691
- 'in Files API',
692
- )
697
+ if not os.path.isdir(local_path):
698
+ raise NotADirectoryError(f'local path is not a directory: {local_path}')
699
+
700
+ if not path:
701
+ path = local_path
702
+
703
+ ignore_files = set()
704
+ if ignore:
705
+ if isinstance(ignore, list):
706
+ for item in ignore:
707
+ ignore_files.update(glob.glob(str(item), recursive=recursive))
708
+ else:
709
+ ignore_files.update(glob.glob(str(ignore), recursive=recursive))
710
+
711
+ for dir_path, _, files in os.walk(str(local_path)):
712
+ for fname in files:
713
+ if ignore_files and fname in ignore_files:
714
+ continue
715
+
716
+ local_file_path = os.path.join(dir_path, fname)
717
+ remote_path = os.path.join(
718
+ path,
719
+ local_file_path.lstrip(str(local_path)),
720
+ )
721
+ self.upload_file(
722
+ local_path=local_file_path,
723
+ path=remote_path,
724
+ overwrite=overwrite,
725
+ )
726
+ return self.info(path)
693
727
 
694
728
  def _upload(
695
729
  self,
@@ -874,15 +908,30 @@ class FileSpace(FileLocation):
874
908
  return False
875
909
  raise
876
910
 
877
- def _list_root_dir(self) -> List[str]:
911
+ def _listdir(self, path: PathLike, *, recursive: bool = False) -> List[str]:
878
912
  """
879
- Return the names of files in the root directory.
913
+ Return the names of files in a directory.
914
+
880
915
  Parameters
881
916
  ----------
917
+ path : Path or str
918
+ Path to the folder
919
+ recursive : bool, optional
920
+ Should folders be listed recursively?
921
+
882
922
  """
883
923
  res = self._manager._get(
884
- f'files/fs/{self._location}',
924
+ f'files/fs/{self._location}/{path}',
885
925
  ).json()
926
+
927
+ if recursive:
928
+ out = []
929
+ for item in res['content'] or []:
930
+ out.append(item['path'])
931
+ if item['type'] == 'directory':
932
+ out.extend(self._listdir(item['path'], recursive=recursive))
933
+ return out
934
+
886
935
  return [x['path'] for x in res['content'] or []]
887
936
 
888
937
  def listdir(
@@ -904,13 +953,17 @@ class FileSpace(FileLocation):
904
953
  List[str]
905
954
 
906
955
  """
907
- if path == '' or path == '/':
908
- return self._list_root_dir()
956
+ path = re.sub(r'^(\./|/)+', r'', str(path))
957
+ path = re.sub(r'/+$', r'', path) + '/'
909
958
 
910
- raise ManagementError(
911
- msg='Operation not supported: directories are currently not allowed '
912
- 'in Files API',
913
- )
959
+ if not self.is_dir(path):
960
+ raise NotADirectoryError(f'path is not a directory: {path}')
961
+
962
+ out = self._listdir(path, recursive=recursive)
963
+ if path != '/':
964
+ path_n = len(path.split('/')) - 1
965
+ out = ['/'.join(x.split('/')[path_n:]) for x in out]
966
+ return out
914
967
 
915
968
  def download_file(
916
969
  self,
@@ -972,17 +1025,28 @@ class FileSpace(FileLocation):
972
1025
  Parameters
973
1026
  ----------
974
1027
  path : Path or str
975
- Path to the file
1028
+ Directory path
976
1029
  local_path : Path or str
977
1030
  Path to local directory target location
978
1031
  overwrite : bool, optional
979
1032
  Should an existing directory / files be overwritten if they exist?
980
1033
 
981
1034
  """
982
- raise ManagementError(
983
- msg='Operation not supported: directories are currently not allowed '
984
- 'in Files API',
985
- )
1035
+
1036
+ if local_path is not None and not overwrite and os.path.exists(local_path):
1037
+ raise OSError('target path already exists; use overwrite=True to replace')
1038
+
1039
+ if not self.is_dir(path):
1040
+ raise NotADirectoryError(f'path is not a directory: {path}')
1041
+
1042
+ files = self.listdir(path, recursive=True)
1043
+ for f in files:
1044
+ remote_path = os.path.join(path, f)
1045
+ if self.is_dir(remote_path):
1046
+ continue
1047
+ target = os.path.normpath(os.path.join(local_path, f))
1048
+ os.makedirs(os.path.dirname(target), exist_ok=True)
1049
+ self.download_file(remote_path, target, overwrite=overwrite)
986
1050
 
987
1051
  def remove(self, path: PathLike) -> None:
988
1052
  """
@@ -1009,10 +1073,10 @@ class FileSpace(FileLocation):
1009
1073
  Path to the file location
1010
1074
 
1011
1075
  """
1012
- raise ManagementError(
1013
- msg='Operation not supported: directories are currently not allowed '
1014
- 'in Files API',
1015
- )
1076
+ if not self.is_dir(path):
1077
+ raise NotADirectoryError('path is not a directory')
1078
+
1079
+ self._manager._delete(f'files/fs/{self._location}/{path}')
1016
1080
 
1017
1081
  def rmdir(self, path: PathLike) -> None:
1018
1082
  """
@@ -43,10 +43,11 @@ class Manager(object):
43
43
  """SingleStoreDB manager base class."""
44
44
 
45
45
  #: Management API version if none is specified.
46
- default_version = config.get_option('management.version')
46
+ default_version = config.get_option('management.version') or 'v1'
47
47
 
48
48
  #: Base URL if none is specified.
49
- default_base_url = config.get_option('management.base_url')
49
+ default_base_url = config.get_option('management.base_url') \
50
+ or 'https://api.singlestore.com'
50
51
 
51
52
  #: Object type
52
53
  obj_type = ''
@@ -1481,10 +1481,11 @@ class WorkspaceManager(Manager):
1481
1481
  """
1482
1482
 
1483
1483
  #: Workspace management API version if none is specified.
1484
- default_version = config.get_option('management.version')
1484
+ default_version = config.get_option('management.version') or 'v1'
1485
1485
 
1486
1486
  #: Base URL if none is specified.
1487
- default_base_url = config.get_option('management.base_url')
1487
+ default_base_url = config.get_option('management.base_url') \
1488
+ or 'https://api.singlestore.com'
1488
1489
 
1489
1490
  #: Object type
1490
1491
  obj_type = 'workspace'
@@ -729,6 +729,7 @@ class Connection(BaseConnection):
729
729
  return
730
730
  if self._closed:
731
731
  raise err.Error('Already closed')
732
+ events.unsubscribe(self._handle_event)
732
733
  self._closed = True
733
734
  if self._sock is None:
734
735
  return
@@ -27,6 +27,22 @@ def subscribe(func: Callable[[Dict[str, Any]], None]) -> None:
27
27
  _subscribers.add(func)
28
28
 
29
29
 
30
+ def unsubscribe(func: Callable[[Dict[str, Any]], None]) -> None:
31
+ """
32
+ Unsubscribe from SingleStore portal events.
33
+
34
+ Parameters
35
+ ----------
36
+ func : Callable
37
+ The function to call when an event is received
38
+
39
+ """
40
+ try:
41
+ _subscribers.remove(func)
42
+ except KeyError:
43
+ pass
44
+
45
+
30
46
  def _event_handler(stream: Any, ident: Any, msg: Dict[str, Any]) -> None:
31
47
  """Handle request on the control stream."""
32
48
  if not _subscribers or not isinstance(msg, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.9.0
3
+ Version: 1.11.0
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,5 +1,5 @@
1
- _singlestoredb_accel.pyd,sha256=y3LZmfycMQRL3Zo8GHdtgDay9EF1ZlAFdSfW1HQpQ7Q,59904
2
- singlestoredb/__init__.py,sha256=uPTzIZk0aLMH0g8uIDrCqusEFDP5oLCegTc5h6XwM3U,1711
1
+ _singlestoredb_accel.pyd,sha256=q-aACKhILot_NGYzmm6WYE3LH_OiU9RANrIBd6YFfO4,59904
2
+ singlestoredb/__init__.py,sha256=Hn7bg_Mioi5UgJH2ZaXgIbOsFdb9Xa1wqDeJvmNEYCU,1712
3
3
  singlestoredb/auth.py,sha256=RmYiH0Wlc2RXc4pTlRMysxtBI445ggCIwojWKC_eDLE,7844
4
4
  singlestoredb/config.py,sha256=n6ludREIoiZDEzSGmv0xouv_zHFnznKNKxSvjzgQ3Lk,12876
5
5
  singlestoredb/connection.py,sha256=I3A0VkA_E6pfUNCzIvj0KTb5zn6-htBAPsu6OVXO9-I,47150
@@ -32,33 +32,37 @@ singlestoredb/functions/ext/rowdat_1.py,sha256=KYj_y5JWm3_B2-QC47HK-CNOrzujBqGUw
32
32
  singlestoredb/functions/ext/utils.py,sha256=OPMFD-tTCx2Kk9jguQkrTr7e4AgNkt15YsvaT1YSmN8,5480
33
33
  singlestoredb/fusion/__init__.py,sha256=FHWtrg6OJFTf6Ye197V5sU6ssryr2h6FBcDIgXP7-H4,367
34
34
  singlestoredb/fusion/graphql.py,sha256=SHqsPe4xgawdsTPHEtJGQlybYGWqPrGMmyK-v20RLac,5420
35
- singlestoredb/fusion/handler.py,sha256=dZ5goR9aseeGbSRTVZpxIYeLsraWE5VTNtA7nskt3rk,28507
35
+ singlestoredb/fusion/handler.py,sha256=ohnU0BIoJ9AHrVLlCHI-3E4Icqoocxqip8T-XyYxBWQ,28293
36
36
  singlestoredb/fusion/registry.py,sha256=_eT1gd38VPlFKs5f9Pu6lqQyoDQ_ixW5O56QwYLQ89Y,6361
37
37
  singlestoredb/fusion/result.py,sha256=EcFY5Qv43ySlQsfl_JB-I3ko7PzVdjuhhoKN96uHSAM,12171
38
38
  singlestoredb/fusion/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- singlestoredb/fusion/handlers/export.py,sha256=mY-lEbi3WZLEHJ91hAFPYcasP4g1VeQkP0xBj3casts,7045
39
+ singlestoredb/fusion/handlers/export.py,sha256=Vf0idiFTluEhDpt7ozWSdYwQcOeXz2-J6-91ax8F834,7189
40
40
  singlestoredb/fusion/handlers/files.py,sha256=sXO5OFS5QsmwNOnMY3G5xpMPqh4_WkssW3dOB6uI4VQ,19669
41
41
  singlestoredb/fusion/handlers/job.py,sha256=3enfxHwERH7T4u0FEwOPN0IL0GtepaCYgEsisiy3Df4,21753
42
+ singlestoredb/fusion/handlers/models.py,sha256=XWaPJQc3GQIOAcjNcxBSGUBJ3xu2qkzQ4ILa40TFQmY,6486
42
43
  singlestoredb/fusion/handlers/stage.py,sha256=PP-SSP204lwpmnycSXXSmFPzoN535JVuwglDCbaQ8Lw,14789
43
- singlestoredb/fusion/handlers/utils.py,sha256=OffWWj8FkZgqr5ijY3WQ6cwaAB7a7Z5cYxLeOAfU_JE,10356
44
+ singlestoredb/fusion/handlers/utils.py,sha256=qwKGKi7hVlxoROZnAdOgqN3Fw4EHxMynfCdUWKvjFvo,10374
44
45
  singlestoredb/fusion/handlers/workspace.py,sha256=NxoEY5xd5lCQmXiim4nhAYCL0agHo1H_rGPpqa31hiw,28397
45
46
  singlestoredb/http/__init__.py,sha256=4cEDvLloGc3LSpU-PnIwacyu0n5oIIIE6xk2SPyWD_w,939
46
47
  singlestoredb/http/connection.py,sha256=LFUeWx7maS7xhQLqEX3pgvIGoosqyJTovtWwJ1DyDSA,40721
48
+ singlestoredb/magics/__init__.py,sha256=fqCBQ0s8o1CYE4Xo_XiSbkLDzLgMNDgpSkOx66-uDZw,1244
49
+ singlestoredb/magics/run_personal.py,sha256=M11xHi9lWquh_pLSpFI89LGE7PhOPQOGqlSPDl48itE,1900
50
+ singlestoredb/magics/run_shared.py,sha256=rnKpW4d8CJvD6ehK8jG8FlxuqZvjZl4KocPTsk-23O8,1805
47
51
  singlestoredb/management/__init__.py,sha256=A66ZnFyX--PsAZ2tvtYUfIUBvVGDBFQsnVc6nGTlX60,277
48
52
  singlestoredb/management/billing_usage.py,sha256=0UHFSPCrN0nyeGFFM-HXS3NP8pYmYo2BCCahDEPXvzg,3883
49
- singlestoredb/management/cluster.py,sha256=XfdBuTlrAG-mnW1BFKeoAr4YSE5IVgxLjbuBSpqIySo,14823
53
+ singlestoredb/management/cluster.py,sha256=auBzNYIXvnI6rq3DNpPgJhwWoT6JsyZRikjpON23Pxg,14867
50
54
  singlestoredb/management/export.py,sha256=Ksrb8_sxeqva4NElaGxdPQUWQga2yOEkf0wnw5M-iGc,4302
51
- singlestoredb/management/files.py,sha256=PmXEeJuIYthGJpjgTaGmb5eMD1lIfJSevOVnf8QDXYk,29204
55
+ singlestoredb/management/files.py,sha256=ly9Hwe0kVWqws8dvjuoIH0fURi8TadTTZPIFKTxnuWI,31677
52
56
  singlestoredb/management/job.py,sha256=Npfe1JLYJlggGBrXLniPKwKUKF1i3alvSY1SFtvauSs,25498
53
- singlestoredb/management/manager.py,sha256=uGNrUe3zhuP-HUqdfwvy4MdEXTCmq-FZKjIwZSc3hOM,9096
57
+ singlestoredb/management/manager.py,sha256=8zU0d7NG83PYMhoAs2JriTqbqh-R2tLX7VZoeZtcogY,9148
54
58
  singlestoredb/management/organization.py,sha256=JBsNC4R3boUKdYvyCZyfGoVMC1mD6SPuMI1UssBVoOM,5611
55
59
  singlestoredb/management/region.py,sha256=oGoLLS88dE1GmY7GCc0BV7X3f7bWwKQyeXOVBFmK9Pk,1678
56
60
  singlestoredb/management/utils.py,sha256=BP-Wb8Sg16GbdLI_DeBz-3ttMklz6ZjYyMOz-sxElz8,13594
57
- singlestoredb/management/workspace.py,sha256=EKSmu7sFBgotcgk0zlQP1Ke6ZnfQxwl_4tY7hev1yO4,58018
61
+ singlestoredb/management/workspace.py,sha256=erII_7SA4vdJkTPBKB6aa88mYujmXTYdNeQZu8KJKLM,58070
58
62
  singlestoredb/mysql/__init__.py,sha256=CbpwzNUJPAmKPpIobC0-ugBta_RgHCMq7X7N75QLReY,4669
59
63
  singlestoredb/mysql/_auth.py,sha256=YaqqyvAHmeraBv3BM207rNveUVPM-mPnW20ts_ynVWg,8341
60
64
  singlestoredb/mysql/charset.py,sha256=mnCdMpvdub1S2mm2PSk2j5JddgsWRjsVLtGx-y9TskE,10724
61
- singlestoredb/mysql/connection.py,sha256=mmTF9X7cPbmTS7yPHHbkjwQGpOPSbpDgZ3MNd2MyXkA,74716
65
+ singlestoredb/mysql/connection.py,sha256=lzwf31pFioWCUaMBB_TahB9VsemKMS0l3DrUqO3ZCJE,74764
62
66
  singlestoredb/mysql/converters.py,sha256=vebFFm6IrC0WgY-5Eh-esaPizY5cq3vDOUlEKGaYM-U,7771
63
67
  singlestoredb/mysql/cursors.py,sha256=pkrP-1t8IhBJRnYpdM7Rdm-332nOq1RYTDJ_yg_q5HI,27682
64
68
  singlestoredb/mysql/err.py,sha256=aDbmfq08gWVmfgIea735wSeiFdvYbB5wusgd3qTVq1s,2480
@@ -128,15 +132,15 @@ singlestoredb/utils/config.py,sha256=WVQ567ZzqzlTGueQH5fEpm5tPZuz8y7qvpEQUB-vPjk
128
132
  singlestoredb/utils/convert_rows.py,sha256=gkZeZazeJvimCYEQ1FdAC-AmMDwmFGCuP6mi653bpns,1885
129
133
  singlestoredb/utils/debug.py,sha256=y7dnJeJGt3U_BWXz9pLt1qNQREpPtumYX_sk1DiqG6Y,362
130
134
  singlestoredb/utils/dtypes.py,sha256=_P2fTX2Fgv9Bcl-2L6KivhWgLzyu91sDamxVnmG92Mw,6103
131
- singlestoredb/utils/events.py,sha256=rC9cHAetua_E1f-EiFkFM-gJzQSQIH5Uk-4sspC3KjI,1188
135
+ singlestoredb/utils/events.py,sha256=Wpp4Z5kw6f7axGAerMirEhgjcAArboQtMc4aqXzfKIc,1519
132
136
  singlestoredb/utils/mogrify.py,sha256=gCcn99-vgsGVjTUV7RHJ6hH4vCNrsGB_Xo4z8kiSPDQ,4201
133
137
  singlestoredb/utils/results.py,sha256=wR70LhCqlobniZf52r67zYLBOKjWHQm68NAskdRQND8,15862
134
138
  singlestoredb/utils/xdict.py,sha256=-wi1lSPTnY99fhVMBhPKJ8cCsQhNG4GMUfkEBDKYgCw,13321
135
139
  sqlx/__init__.py,sha256=4Sdn8HN-Hf8v0_wCt60DCckCg8BvgM3-9r4YVfZycRE,89
136
140
  sqlx/magic.py,sha256=6VBlotgjautjev599tHaTYOfcfOA9m6gV_-P1_Qc4lI,3622
137
- singlestoredb-1.9.0.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
138
- singlestoredb-1.9.0.dist-info/METADATA,sha256=-R-w0V56XC6oKl6i-EgytHVnL1Z1_vhuJNG4aLAnj5o,5710
139
- singlestoredb-1.9.0.dist-info/WHEEL,sha256=c4k7z5HB0t-y0nBCv6KyJ6KCjn8SEGPddD0lhaPtU3E,96
140
- singlestoredb-1.9.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
141
- singlestoredb-1.9.0.dist-info/top_level.txt,sha256=lA65Vf4qAMfg_s1oG3LEO90h4t1Z-SPDbRqkevI3bSY,40
142
- singlestoredb-1.9.0.dist-info/RECORD,,
141
+ singlestoredb-1.11.0.dist-info/LICENSE,sha256=Bojenzui8aPNjlF3w4ojguDP7sTf8vFV_9Gc2UAG1sg,11542
142
+ singlestoredb-1.11.0.dist-info/METADATA,sha256=8tDiZ6TwCEXStAL2L0YaT2LGv5f3B2XrT-KU6K5XS4A,5711
143
+ singlestoredb-1.11.0.dist-info/WHEEL,sha256=c4k7z5HB0t-y0nBCv6KyJ6KCjn8SEGPddD0lhaPtU3E,96
144
+ singlestoredb-1.11.0.dist-info/entry_points.txt,sha256=bSLaTWB5zGjpVYPAaI46MkkDup0su-eb3uAhCNYuRV0,48
145
+ singlestoredb-1.11.0.dist-info/top_level.txt,sha256=lA65Vf4qAMfg_s1oG3LEO90h4t1Z-SPDbRqkevI3bSY,40
146
+ singlestoredb-1.11.0.dist-info/RECORD,,