singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__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 (120) hide show
  1. singlestoredb/__init__.py +33 -1
  2. singlestoredb/alchemy/__init__.py +90 -0
  3. singlestoredb/auth.py +5 -1
  4. singlestoredb/config.py +116 -14
  5. singlestoredb/connection.py +483 -516
  6. singlestoredb/converters.py +238 -135
  7. singlestoredb/exceptions.py +30 -2
  8. singlestoredb/functions/__init__.py +1 -0
  9. singlestoredb/functions/decorator.py +142 -0
  10. singlestoredb/functions/dtypes.py +1639 -0
  11. singlestoredb/functions/ext/__init__.py +2 -0
  12. singlestoredb/functions/ext/arrow.py +375 -0
  13. singlestoredb/functions/ext/asgi.py +661 -0
  14. singlestoredb/functions/ext/json.py +427 -0
  15. singlestoredb/functions/ext/mmap.py +306 -0
  16. singlestoredb/functions/ext/rowdat_1.py +744 -0
  17. singlestoredb/functions/signature.py +673 -0
  18. singlestoredb/fusion/__init__.py +11 -0
  19. singlestoredb/fusion/graphql.py +213 -0
  20. singlestoredb/fusion/handler.py +621 -0
  21. singlestoredb/fusion/handlers/stage.py +257 -0
  22. singlestoredb/fusion/handlers/utils.py +162 -0
  23. singlestoredb/fusion/handlers/workspace.py +412 -0
  24. singlestoredb/fusion/registry.py +164 -0
  25. singlestoredb/fusion/result.py +399 -0
  26. singlestoredb/http/__init__.py +27 -0
  27. singlestoredb/{http.py → http/connection.py} +555 -154
  28. singlestoredb/management/__init__.py +3 -0
  29. singlestoredb/management/billing_usage.py +148 -0
  30. singlestoredb/management/cluster.py +14 -6
  31. singlestoredb/management/manager.py +100 -38
  32. singlestoredb/management/organization.py +188 -0
  33. singlestoredb/management/region.py +5 -5
  34. singlestoredb/management/utils.py +281 -2
  35. singlestoredb/management/workspace.py +1344 -49
  36. singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
  37. singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
  38. singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
  39. singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
  40. singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
  41. singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
  42. singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
  43. singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
  44. singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
  45. singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
  46. singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
  47. singlestoredb/mysql/converters.py +271 -0
  48. singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
  49. singlestoredb/mysql/err.py +92 -0
  50. singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
  51. singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
  52. singlestoredb/mysql/tests/__init__.py +19 -0
  53. singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
  54. singlestoredb/mysql/tests/conftest.py +37 -0
  55. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
  56. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
  57. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
  58. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
  59. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
  60. singlestoredb/mysql/tests/test_cursor.py +141 -0
  61. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
  62. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
  63. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
  64. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
  65. singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
  66. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
  67. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  68. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
  69. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
  70. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
  71. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
  72. singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
  73. singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
  74. singlestoredb/pytest.py +283 -0
  75. singlestoredb/tests/empty.sql +0 -0
  76. singlestoredb/tests/ext_funcs/__init__.py +385 -0
  77. singlestoredb/tests/test.sql +210 -0
  78. singlestoredb/tests/test2.sql +1 -0
  79. singlestoredb/tests/test_basics.py +482 -115
  80. singlestoredb/tests/test_config.py +13 -13
  81. singlestoredb/tests/test_connection.py +241 -305
  82. singlestoredb/tests/test_dbapi.py +27 -0
  83. singlestoredb/tests/test_ext_func.py +1193 -0
  84. singlestoredb/tests/test_ext_func_data.py +1101 -0
  85. singlestoredb/tests/test_fusion.py +465 -0
  86. singlestoredb/tests/test_http.py +32 -26
  87. singlestoredb/tests/test_management.py +588 -8
  88. singlestoredb/tests/test_plugin.py +33 -0
  89. singlestoredb/tests/test_results.py +11 -12
  90. singlestoredb/tests/test_udf.py +687 -0
  91. singlestoredb/tests/utils.py +3 -2
  92. singlestoredb/utils/config.py +58 -0
  93. singlestoredb/utils/debug.py +13 -0
  94. singlestoredb/utils/mogrify.py +151 -0
  95. singlestoredb/utils/results.py +4 -1
  96. singlestoredb-1.0.4.dist-info/METADATA +139 -0
  97. singlestoredb-1.0.4.dist-info/RECORD +112 -0
  98. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
  99. singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
  100. singlestoredb/clients/pymysqlsv/converters.py +0 -365
  101. singlestoredb/clients/pymysqlsv/err.py +0 -144
  102. singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
  103. singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
  104. singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
  105. singlestoredb/drivers/__init__.py +0 -45
  106. singlestoredb/drivers/base.py +0 -198
  107. singlestoredb/drivers/cymysql.py +0 -38
  108. singlestoredb/drivers/http.py +0 -47
  109. singlestoredb/drivers/mariadb.py +0 -40
  110. singlestoredb/drivers/mysqlconnector.py +0 -49
  111. singlestoredb/drivers/mysqldb.py +0 -60
  112. singlestoredb/drivers/pymysql.py +0 -37
  113. singlestoredb/drivers/pymysqlsv.py +0 -35
  114. singlestoredb/drivers/pyodbc.py +0 -65
  115. singlestoredb-0.4.0.dist-info/METADATA +0 -111
  116. singlestoredb-0.4.0.dist-info/RECORD +0 -86
  117. /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
  118. /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
  119. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
  120. {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env python3
2
+ from typing import Any
3
+ from typing import Dict
4
+ from typing import Optional
5
+
6
+ from .. import result
7
+ from ..handler import SQLHandler
8
+ from ..result import FusionSQLResult
9
+ from .utils import dt_isoformat
10
+ from .utils import get_workspace_group
11
+
12
+
13
+ class ShowStageFilesHandler(SQLHandler):
14
+ """
15
+ SHOW STAGE FILES [ in_group ] [ at_path ] [ <like> ] [ <order-by> ]
16
+ [ <limit> ] [ recursive ] [ extended ];
17
+
18
+ # Workspace group
19
+ in_group = IN GROUP { group_id | group_name }
20
+
21
+ # ID of group
22
+ group_id = ID '<group-id>'
23
+
24
+ # Name of group
25
+ group_name = '<group-name>'
26
+
27
+ # Stage path to list
28
+ at_path = AT '<path>'
29
+
30
+ # Should the listing be recursive?
31
+ recursive = RECURSIVE
32
+
33
+ # Should extended attributes be shown?
34
+ extended = EXTENDED
35
+
36
+ """
37
+
38
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
39
+ wg = get_workspace_group(params)
40
+
41
+ res = FusionSQLResult()
42
+ res.add_field('Name', result.STRING)
43
+
44
+ if params['extended']:
45
+ res.add_field('Type', result.STRING)
46
+ res.add_field('Size', result.INTEGER)
47
+ res.add_field('Writable', result.STRING)
48
+ res.add_field('CreatedAt', result.DATETIME)
49
+ res.add_field('LastModifiedAt', result.DATETIME)
50
+
51
+ files = []
52
+ for x in wg.stage.listdir(
53
+ params['at_path'] or '/',
54
+ recursive=params['recursive'],
55
+ ):
56
+ info = wg.stage.info(x)
57
+ files.append(
58
+ tuple([
59
+ x, info.type, info.size or 0, info.writable,
60
+ dt_isoformat(info.created_at),
61
+ dt_isoformat(info.last_modified_at),
62
+ ]),
63
+ )
64
+ res.set_rows(files)
65
+
66
+ else:
67
+ res.set_rows([(x,) for x in wg.stage.listdir(
68
+ params['at_path'] or '/',
69
+ recursive=params['recursive'],
70
+ )])
71
+
72
+ if params['like']:
73
+ res = res.like(Name=params['like'])
74
+
75
+ return res.order_by(**params['order_by']).limit(params['limit'])
76
+
77
+
78
+ ShowStageFilesHandler.register(overwrite=True)
79
+
80
+
81
+ class UploadStageFileHandler(SQLHandler):
82
+ """
83
+ UPLOAD FILE TO STAGE stage_path [ in_group ] FROM local_path [ overwrite ];
84
+
85
+ # Path to stage file
86
+ stage_path = '<stage-path>'
87
+
88
+ # Workspace group
89
+ in_group = IN GROUP { group_id | group_name }
90
+
91
+ # ID of group
92
+ group_id = ID '<group-id>'
93
+
94
+ # Name of group
95
+ group_name = '<group-name>'
96
+
97
+ # Path to local file
98
+ local_path = '<local-path>'
99
+
100
+ # Should an existing file be overwritten?
101
+ overwrite = OVERWRITE
102
+
103
+ """
104
+
105
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
106
+ wg = get_workspace_group(params)
107
+ wg.stage.upload_file(
108
+ params['local_path'], params['stage_path'],
109
+ overwrite=params['overwrite'],
110
+ )
111
+ return None
112
+
113
+
114
+ UploadStageFileHandler.register(overwrite=True)
115
+
116
+
117
+ class DownloadStageFileHandler(SQLHandler):
118
+ """
119
+ DOWNLOAD STAGE FILE stage_path [ in_group ] [ local_path ]
120
+ [ overwrite ] [ encoding ];
121
+
122
+ # Path to stage file
123
+ stage_path = '<stage-path>'
124
+
125
+ # Workspace group
126
+ in_group = IN GROUP { group_id | group_name }
127
+
128
+ # ID of group
129
+ group_id = ID '<group-id>'
130
+
131
+ # Name of group
132
+ group_name = '<group-name>'
133
+
134
+ # Path to local file
135
+ local_path = TO '<local-path>'
136
+
137
+ # Should an existing file be overwritten?
138
+ overwrite = OVERWRITE
139
+
140
+ # File encoding
141
+ encoding = ENCODING '<encoding>'
142
+
143
+ """
144
+
145
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
146
+ wg = get_workspace_group(params)
147
+
148
+ out = wg.stage.download_file(
149
+ params['stage_path'],
150
+ local_path=params['local_path'] or None,
151
+ overwrite=params['overwrite'],
152
+ encoding=params['encoding'] or None,
153
+ )
154
+
155
+ if not params['local_path']:
156
+ res = FusionSQLResult()
157
+ if params['encoding']:
158
+ res.add_field('Data', result.STRING)
159
+ else:
160
+ res.add_field('Data', result.BLOB)
161
+ res.set_rows([(out,)])
162
+ return res
163
+
164
+ return None
165
+
166
+
167
+ DownloadStageFileHandler.register(overwrite=True)
168
+
169
+
170
+ class DropStageFileHandler(SQLHandler):
171
+ """
172
+ DROP STAGE FILE stage_path [ in_group ];
173
+
174
+ # Path to stage file
175
+ stage_path = '<stage-path>'
176
+
177
+ # Workspace group
178
+ in_group = IN GROUP { group_id | group_name }
179
+
180
+ # ID of group
181
+ group_id = ID '<group-id>'
182
+
183
+ # Name of group
184
+ group_name = '<group-name>'
185
+
186
+ """
187
+
188
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
189
+ wg = get_workspace_group(params)
190
+ wg.stage.remove(params['stage_path'])
191
+ return None
192
+
193
+
194
+ DropStageFileHandler.register(overwrite=True)
195
+
196
+
197
+ class DropStageFolderHandler(SQLHandler):
198
+ """
199
+ DROP STAGE FOLDER stage_path [ in_group ] [ recursive ];
200
+
201
+ # Path to stage folder
202
+ stage_path = '<stage-path>'
203
+
204
+ # Workspace group
205
+ in_group = IN GROUP { group_id | group_name }
206
+
207
+ # ID of group
208
+ group_id = ID '<group-id>'
209
+
210
+ # Name of group
211
+ group_name = '<group-name>'
212
+
213
+ # Should folers be deleted recursively?
214
+ recursive = RECURSIVE
215
+
216
+ """
217
+
218
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
219
+ wg = get_workspace_group(params)
220
+ if params['recursive']:
221
+ wg.stage.removedirs(params['stage_path'])
222
+ else:
223
+ wg.stage.rmdir(params['stage_path'])
224
+ return None
225
+
226
+
227
+ DropStageFolderHandler.register(overwrite=True)
228
+
229
+
230
+ class CreateStageFolderHandler(SQLHandler):
231
+ """
232
+ CREATE STAGE FOLDER stage_path [ in_group ] [ overwrite ];
233
+
234
+ # Workspace group
235
+ in_group = IN GROUP { group_id | group_name }
236
+
237
+ # ID of group
238
+ group_id = ID '<group-id>'
239
+
240
+ # Name of group
241
+ group_name = '<group-name>'
242
+
243
+ # Path to stage folder
244
+ stage_path = '<stage-path>'
245
+
246
+ # Should an existing folder be overwritten?
247
+ overwrite = OVERWRITE
248
+
249
+ """
250
+
251
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
252
+ wg = get_workspace_group(params)
253
+ wg.stage.mkdir(params['stage_path'], overwrite=params['overwrite'])
254
+ return None
255
+
256
+
257
+ CreateStageFolderHandler.register(overwrite=True)
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env python
2
+ import datetime
3
+ import os
4
+ from typing import Any
5
+ from typing import Dict
6
+ from typing import Optional
7
+
8
+ from ...exceptions import ManagementError
9
+ from ...management import manage_workspaces
10
+ from ...management.workspace import Workspace
11
+ from ...management.workspace import WorkspaceGroup
12
+ from ...management.workspace import WorkspaceManager
13
+
14
+
15
+ def get_workspace_manager() -> WorkspaceManager:
16
+ """Return a new workspace manager."""
17
+ return manage_workspaces()
18
+
19
+
20
+ def dt_isoformat(dt: Optional[datetime.datetime]) -> Optional[str]:
21
+ """Convert datetime to string."""
22
+ if dt is None:
23
+ return None
24
+ return dt.isoformat()
25
+
26
+
27
+ def get_workspace_group(params: Dict[str, Any]) -> WorkspaceGroup:
28
+ """
29
+ Find a workspace group matching group_id or group_name.
30
+
31
+ This function will get a workspace group or ID from the
32
+ following parameters:
33
+
34
+ * params['group_name']
35
+ * params['group_id']
36
+ * params['group']['group_name']
37
+ * params['group']['group_id']
38
+ * params['in_group']['group_name']
39
+ * params['in_group']['group_id']
40
+
41
+ Or, from the SINGLESTOREDB_WORKSPACE_GROUP environment variable.
42
+
43
+ """
44
+ manager = get_workspace_manager()
45
+
46
+ group_name = params.get('group_name') or \
47
+ (params.get('in_group') or {}).get('group_name') or \
48
+ (params.get('group') or {}).get('group_name')
49
+ if group_name:
50
+ workspace_groups = [
51
+ x for x in manager.workspace_groups
52
+ if x.name == group_name
53
+ ]
54
+
55
+ if not workspace_groups:
56
+ raise KeyError(
57
+ f'no workspace group found with name: {group_name}',
58
+ )
59
+
60
+ if len(workspace_groups) > 1:
61
+ ids = ', '.join(x.id for x in workspace_groups)
62
+ raise ValueError(
63
+ f'more than one workspace group with given name was found: {ids}',
64
+ )
65
+
66
+ return workspace_groups[0]
67
+
68
+ group_id = params.get('group_id') or \
69
+ (params.get('in_group') or {}).get('group_id') or \
70
+ (params.get('group') or {}).get('group_id')
71
+ if group_id:
72
+ try:
73
+ return manager.get_workspace_group(group_id)
74
+ except ManagementError as exc:
75
+ if exc.errno == 404:
76
+ raise KeyError(f'no workspace group found with ID: {group_id}')
77
+ raise
78
+
79
+ if os.environ.get('SINGLESTOREDB_WORKSPACE_GROUP'):
80
+ try:
81
+ return manager.get_workspace_group(
82
+ os.environ['SINGLESTOREDB_WORKSPACE_GROUP'],
83
+ )
84
+ except ManagementError as exc:
85
+ if exc.errno == 404:
86
+ raise KeyError(
87
+ 'no workspace found with ID: '
88
+ f'{os.environ["SINGLESTOREDB_WORKSPACE_GROUP"]}',
89
+ )
90
+ raise
91
+
92
+ if os.environ.get('SINGLESTOREDB_CLUSTER'):
93
+ raise ValueError('clusters and shared workspaces are not currently supported')
94
+
95
+ raise KeyError('no workspace group was specified')
96
+
97
+
98
+ def get_workspace(params: Dict[str, Any]) -> Workspace:
99
+ """
100
+ Retrieve the specified workspace.
101
+
102
+ This function will get a workspace group or ID from the
103
+ following parameters:
104
+
105
+ * params['workspace_name']
106
+ * params['workspace_id']
107
+ * params['workspace']['workspace_name']
108
+ * params['workspace']['workspace_id']
109
+
110
+ Or, from the SINGLESTOREDB_WORKSPACE environment variable.
111
+
112
+ """
113
+ manager = get_workspace_manager()
114
+ workspace_name = params.get('workspace_name') or \
115
+ (params.get('workspace') or {}).get('workspace_name')
116
+ if workspace_name:
117
+ wg = get_workspace_group(params)
118
+ workspaces = [
119
+ x for x in wg.workspaces
120
+ if x.name == workspace_name
121
+ ]
122
+
123
+ if not workspaces:
124
+ raise KeyError(
125
+ f'no workspace found with name: {workspace_name}',
126
+ )
127
+
128
+ if len(workspaces) > 1:
129
+ ids = ', '.join(x.id for x in workspaces)
130
+ raise ValueError(
131
+ f'more than one workspace with given name was found: {ids}',
132
+ )
133
+
134
+ return workspaces[0]
135
+
136
+ workspace_id = params.get('workspace_id') or \
137
+ (params.get('workspace') or {}).get('workspace_id')
138
+ if workspace_id:
139
+ try:
140
+ return manager.get_workspace(workspace_id)
141
+ except ManagementError as exc:
142
+ if exc.errno == 404:
143
+ raise KeyError(f'no workspace found with ID: {workspace_id}')
144
+ raise
145
+
146
+ if os.environ.get('SINGLESTOREDB_WORKSPACE'):
147
+ try:
148
+ return manager.get_workspace(
149
+ os.environ['SINGLESTOREDB_WORKSPACE'],
150
+ )
151
+ except ManagementError as exc:
152
+ if exc.errno == 404:
153
+ raise KeyError(
154
+ 'no workspace found with ID: '
155
+ f'{os.environ["SINGLESTOREDB_WORKSPACE"]}',
156
+ )
157
+ raise
158
+
159
+ if os.environ.get('SINGLESTOREDB_CLUSTER'):
160
+ raise ValueError('clusters and shared workspaces are not currently supported')
161
+
162
+ raise KeyError('no workspace was specified')