singlestoredb 1.16.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.
Files changed (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. sqlx/magic.py +113 -0
@@ -0,0 +1,887 @@
1
+ #!/usr/bin/env python
2
+ """SingleStoreDB Cloud Scheduled Notebook Job."""
3
+ import datetime
4
+ import time
5
+ from enum import Enum
6
+ from typing import Any
7
+ from typing import Dict
8
+ from typing import List
9
+ from typing import Optional
10
+ from typing import Type
11
+ from typing import Union
12
+
13
+ from ..exceptions import ManagementError
14
+ from .manager import Manager
15
+ from .utils import camel_to_snake
16
+ from .utils import from_datetime
17
+ from .utils import get_cluster_id
18
+ from .utils import get_database_name
19
+ from .utils import get_virtual_workspace_id
20
+ from .utils import get_workspace_id
21
+ from .utils import to_datetime
22
+ from .utils import to_datetime_strict
23
+ from .utils import vars_to_str
24
+
25
+
26
+ type_to_parameter_conversion_map = {
27
+ str: 'string',
28
+ int: 'integer',
29
+ float: 'float',
30
+ bool: 'boolean',
31
+ }
32
+
33
+
34
+ class Mode(Enum):
35
+ ONCE = 'Once'
36
+ RECURRING = 'Recurring'
37
+
38
+ @classmethod
39
+ def from_str(cls, s: str) -> 'Mode':
40
+ try:
41
+ return cls[str(camel_to_snake(s)).upper()]
42
+ except KeyError:
43
+ raise ValueError(f'Unknown Mode: {s}')
44
+
45
+ def __str__(self) -> str:
46
+ """Return string representation."""
47
+ return self.value
48
+
49
+ def __repr__(self) -> str:
50
+ """Return string representation."""
51
+ return str(self)
52
+
53
+
54
+ class TargetType(Enum):
55
+ WORKSPACE = 'Workspace'
56
+ CLUSTER = 'Cluster'
57
+ VIRTUAL_WORKSPACE = 'VirtualWorkspace'
58
+
59
+ @classmethod
60
+ def from_str(cls, s: str) -> 'TargetType':
61
+ try:
62
+ return cls[str(camel_to_snake(s)).upper()]
63
+ except KeyError:
64
+ raise ValueError(f'Unknown TargetType: {s}')
65
+
66
+ def __str__(self) -> str:
67
+ """Return string representation."""
68
+ return self.value
69
+
70
+ def __repr__(self) -> str:
71
+ """Return string representation."""
72
+ return str(self)
73
+
74
+
75
+ class Status(Enum):
76
+ UNKNOWN = 'Unknown'
77
+ SCHEDULED = 'Scheduled'
78
+ RUNNING = 'Running'
79
+ COMPLETED = 'Completed'
80
+ FAILED = 'Failed'
81
+ ERROR = 'Error'
82
+ CANCELED = 'Canceled'
83
+
84
+ @classmethod
85
+ def from_str(cls, s: str) -> 'Status':
86
+ try:
87
+ return cls[str(camel_to_snake(s)).upper()]
88
+ except KeyError:
89
+ return cls.UNKNOWN
90
+
91
+ def __str__(self) -> str:
92
+ """Return string representation."""
93
+ return self.value
94
+
95
+ def __repr__(self) -> str:
96
+ """Return string representation."""
97
+ return str(self)
98
+
99
+
100
+ class Parameter(object):
101
+
102
+ name: str
103
+ value: str
104
+ type: str
105
+
106
+ def __init__(
107
+ self,
108
+ name: str,
109
+ value: str,
110
+ type: str,
111
+ ):
112
+ self.name = name
113
+ self.value = value
114
+ self.type = type
115
+
116
+ @classmethod
117
+ def from_dict(cls, obj: Dict[str, Any]) -> 'Parameter':
118
+ """
119
+ Construct a Parameter from a dictionary of values.
120
+
121
+ Parameters
122
+ ----------
123
+ obj : dict
124
+ Dictionary of values
125
+
126
+ Returns
127
+ -------
128
+ :class:`Parameter`
129
+
130
+ """
131
+ out = cls(
132
+ name=obj['name'],
133
+ value=obj['value'],
134
+ type=obj['type'],
135
+ )
136
+
137
+ return out
138
+
139
+ def __str__(self) -> str:
140
+ """Return string representation."""
141
+ return vars_to_str(self)
142
+
143
+ def __repr__(self) -> str:
144
+ """Return string representation."""
145
+ return str(self)
146
+
147
+
148
+ class Runtime(object):
149
+
150
+ name: str
151
+ description: str
152
+
153
+ def __init__(
154
+ self,
155
+ name: str,
156
+ description: str,
157
+ ):
158
+ self.name = name
159
+ self.description = description
160
+
161
+ @classmethod
162
+ def from_dict(cls, obj: Dict[str, Any]) -> 'Runtime':
163
+ """
164
+ Construct a Runtime from a dictionary of values.
165
+
166
+ Parameters
167
+ ----------
168
+ obj : dict
169
+ Dictionary of values
170
+
171
+ Returns
172
+ -------
173
+ :class:`Runtime`
174
+
175
+ """
176
+ out = cls(
177
+ name=obj['name'],
178
+ description=obj['description'],
179
+ )
180
+
181
+ return out
182
+
183
+ def __str__(self) -> str:
184
+ """Return string representation."""
185
+ return vars_to_str(self)
186
+
187
+ def __repr__(self) -> str:
188
+ """Return string representation."""
189
+ return str(self)
190
+
191
+
192
+ class JobMetadata(object):
193
+
194
+ avg_duration_in_seconds: Optional[float]
195
+ count: int
196
+ max_duration_in_seconds: Optional[float]
197
+ status: Status
198
+
199
+ def __init__(
200
+ self,
201
+ avg_duration_in_seconds: Optional[float],
202
+ count: int,
203
+ max_duration_in_seconds: Optional[float],
204
+ status: Status,
205
+ ):
206
+ self.avg_duration_in_seconds = avg_duration_in_seconds
207
+ self.count = count
208
+ self.max_duration_in_seconds = max_duration_in_seconds
209
+ self.status = status
210
+
211
+ @classmethod
212
+ def from_dict(cls, obj: Dict[str, Any]) -> 'JobMetadata':
213
+ """
214
+ Construct a JobMetadata from a dictionary of values.
215
+
216
+ Parameters
217
+ ----------
218
+ obj : dict
219
+ Dictionary of values
220
+
221
+ Returns
222
+ -------
223
+ :class:`JobMetadata`
224
+
225
+ """
226
+ out = cls(
227
+ avg_duration_in_seconds=obj.get('avgDurationInSeconds'),
228
+ count=obj['count'],
229
+ max_duration_in_seconds=obj.get('maxDurationInSeconds'),
230
+ status=Status.from_str(obj['status']),
231
+ )
232
+
233
+ return out
234
+
235
+ def __str__(self) -> str:
236
+ """Return string representation."""
237
+ return vars_to_str(self)
238
+
239
+ def __repr__(self) -> str:
240
+ """Return string representation."""
241
+ return str(self)
242
+
243
+
244
+ class ExecutionMetadata(object):
245
+
246
+ start_execution_number: int
247
+ end_execution_number: int
248
+
249
+ def __init__(
250
+ self,
251
+ start_execution_number: int,
252
+ end_execution_number: int,
253
+ ):
254
+ self.start_execution_number = start_execution_number
255
+ self.end_execution_number = end_execution_number
256
+
257
+ @classmethod
258
+ def from_dict(cls, obj: Dict[str, Any]) -> 'ExecutionMetadata':
259
+ """
260
+ Construct an ExecutionMetadata from a dictionary of values.
261
+
262
+ Parameters
263
+ ----------
264
+ obj : dict
265
+ Dictionary of values
266
+
267
+ Returns
268
+ -------
269
+ :class:`ExecutionMetadata`
270
+
271
+ """
272
+ out = cls(
273
+ start_execution_number=obj['startExecutionNumber'],
274
+ end_execution_number=obj['endExecutionNumber'],
275
+ )
276
+
277
+ return out
278
+
279
+ def __str__(self) -> str:
280
+ """Return string representation."""
281
+ return vars_to_str(self)
282
+
283
+ def __repr__(self) -> str:
284
+ """Return string representation."""
285
+ return str(self)
286
+
287
+
288
+ class Execution(object):
289
+
290
+ execution_id: str
291
+ job_id: str
292
+ status: Status
293
+ snapshot_notebook_path: Optional[str]
294
+ scheduled_start_time: datetime.datetime
295
+ started_at: Optional[datetime.datetime]
296
+ finished_at: Optional[datetime.datetime]
297
+ execution_number: int
298
+
299
+ def __init__(
300
+ self,
301
+ execution_id: str,
302
+ job_id: str,
303
+ status: Status,
304
+ scheduled_start_time: datetime.datetime,
305
+ started_at: Optional[datetime.datetime],
306
+ finished_at: Optional[datetime.datetime],
307
+ execution_number: int,
308
+ snapshot_notebook_path: Optional[str],
309
+ ):
310
+ self.execution_id = execution_id
311
+ self.job_id = job_id
312
+ self.status = status
313
+ self.scheduled_start_time = scheduled_start_time
314
+ self.started_at = started_at
315
+ self.finished_at = finished_at
316
+ self.execution_number = execution_number
317
+ self.snapshot_notebook_path = snapshot_notebook_path
318
+
319
+ @classmethod
320
+ def from_dict(cls, obj: Dict[str, Any]) -> 'Execution':
321
+ """
322
+ Construct an Execution from a dictionary of values.
323
+
324
+ Parameters
325
+ ----------
326
+ obj : dict
327
+ Dictionary of values
328
+
329
+ Returns
330
+ -------
331
+ :class:`Execution`
332
+
333
+ """
334
+ out = cls(
335
+ execution_id=obj['executionID'],
336
+ job_id=obj['jobID'],
337
+ status=Status.from_str(obj['status']),
338
+ snapshot_notebook_path=obj.get('snapshotNotebookPath'),
339
+ scheduled_start_time=to_datetime_strict(obj['scheduledStartTime']),
340
+ started_at=to_datetime(obj.get('startedAt')),
341
+ finished_at=to_datetime(obj.get('finishedAt')),
342
+ execution_number=obj['executionNumber'],
343
+ )
344
+
345
+ return out
346
+
347
+ def __str__(self) -> str:
348
+ """Return string representation."""
349
+ return vars_to_str(self)
350
+
351
+ def __repr__(self) -> str:
352
+ """Return string representation."""
353
+ return str(self)
354
+
355
+
356
+ class ExecutionsData(object):
357
+
358
+ executions: List[Execution]
359
+ metadata: ExecutionMetadata
360
+
361
+ def __init__(
362
+ self,
363
+ executions: List[Execution],
364
+ metadata: ExecutionMetadata,
365
+ ):
366
+ self.executions = executions
367
+ self.metadata = metadata
368
+
369
+ @classmethod
370
+ def from_dict(cls, obj: Dict[str, Any]) -> 'ExecutionsData':
371
+ """
372
+ Construct an ExecutionsData from a dictionary of values.
373
+
374
+ Parameters
375
+ ----------
376
+ obj : dict
377
+ Dictionary of values
378
+
379
+ Returns
380
+ -------
381
+ :class:`ExecutionsData`
382
+
383
+ """
384
+ out = cls(
385
+ executions=[Execution.from_dict(x) for x in obj['executions']],
386
+ metadata=ExecutionMetadata.from_dict(obj['executionsMetadata']),
387
+ )
388
+
389
+ return out
390
+
391
+ def __str__(self) -> str:
392
+ """Return string representation."""
393
+ return vars_to_str(self)
394
+
395
+ def __repr__(self) -> str:
396
+ """Return string representation."""
397
+ return str(self)
398
+
399
+
400
+ class ExecutionConfig(object):
401
+
402
+ create_snapshot: bool
403
+ max_duration_in_mins: int
404
+ notebook_path: str
405
+
406
+ def __init__(
407
+ self,
408
+ create_snapshot: bool,
409
+ max_duration_in_mins: int,
410
+ notebook_path: str,
411
+ ):
412
+ self.create_snapshot = create_snapshot
413
+ self.max_duration_in_mins = max_duration_in_mins
414
+ self.notebook_path = notebook_path
415
+
416
+ @classmethod
417
+ def from_dict(cls, obj: Dict[str, Any]) -> 'ExecutionConfig':
418
+ """
419
+ Construct an ExecutionConfig from a dictionary of values.
420
+
421
+ Parameters
422
+ ----------
423
+ obj : dict
424
+ Dictionary of values
425
+
426
+ Returns
427
+ -------
428
+ :class:`ExecutionConfig`
429
+
430
+ """
431
+ out = cls(
432
+ create_snapshot=obj['createSnapshot'],
433
+ max_duration_in_mins=obj['maxAllowedExecutionDurationInMinutes'],
434
+ notebook_path=obj['notebookPath'],
435
+ )
436
+
437
+ return out
438
+
439
+ def __str__(self) -> str:
440
+ """Return string representation."""
441
+ return vars_to_str(self)
442
+
443
+ def __repr__(self) -> str:
444
+ """Return string representation."""
445
+ return str(self)
446
+
447
+
448
+ class Schedule(object):
449
+
450
+ execution_interval_in_minutes: Optional[int]
451
+ mode: Mode
452
+ start_at: Optional[datetime.datetime]
453
+
454
+ def __init__(
455
+ self,
456
+ execution_interval_in_minutes: Optional[int],
457
+ mode: Mode,
458
+ start_at: Optional[datetime.datetime],
459
+ ):
460
+ self.execution_interval_in_minutes = execution_interval_in_minutes
461
+ self.mode = mode
462
+ self.start_at = start_at
463
+
464
+ @classmethod
465
+ def from_dict(cls, obj: Dict[str, Any]) -> 'Schedule':
466
+ """
467
+ Construct a Schedule from a dictionary of values.
468
+
469
+ Parameters
470
+ ----------
471
+ obj : dict
472
+ Dictionary of values
473
+
474
+ Returns
475
+ -------
476
+ :class:`Schedule`
477
+
478
+ """
479
+ out = cls(
480
+ execution_interval_in_minutes=obj.get('executionIntervalInMinutes'),
481
+ mode=Mode.from_str(obj['mode']),
482
+ start_at=to_datetime(obj.get('startAt')),
483
+ )
484
+
485
+ return out
486
+
487
+ def __str__(self) -> str:
488
+ """Return string representation."""
489
+ return vars_to_str(self)
490
+
491
+ def __repr__(self) -> str:
492
+ """Return string representation."""
493
+ return str(self)
494
+
495
+
496
+ class TargetConfig(object):
497
+
498
+ database_name: Optional[str]
499
+ resume_target: bool
500
+ target_id: str
501
+ target_type: TargetType
502
+
503
+ def __init__(
504
+ self,
505
+ database_name: Optional[str],
506
+ resume_target: bool,
507
+ target_id: str,
508
+ target_type: TargetType,
509
+ ):
510
+ self.database_name = database_name
511
+ self.resume_target = resume_target
512
+ self.target_id = target_id
513
+ self.target_type = target_type
514
+
515
+ @classmethod
516
+ def from_dict(cls, obj: Dict[str, Any]) -> 'TargetConfig':
517
+ """
518
+ Construct a TargetConfig from a dictionary of values.
519
+
520
+ Parameters
521
+ ----------
522
+ obj : dict
523
+ Dictionary of values
524
+
525
+ Returns
526
+ -------
527
+ :class:`TargetConfig`
528
+
529
+ """
530
+ out = cls(
531
+ database_name=obj.get('databaseName'),
532
+ resume_target=obj['resumeTarget'],
533
+ target_id=obj['targetID'],
534
+ target_type=TargetType.from_str(obj['targetType']),
535
+ )
536
+
537
+ return out
538
+
539
+ def __str__(self) -> str:
540
+ """Return string representation."""
541
+ return vars_to_str(self)
542
+
543
+ def __repr__(self) -> str:
544
+ """Return string representation."""
545
+ return str(self)
546
+
547
+
548
+ class Job(object):
549
+ """
550
+ Scheduled Notebook Job definition.
551
+
552
+ This object is not directly instantiated. It is used in results
553
+ of API calls on the :class:`JobsManager`. See :meth:`JobsManager.run`.
554
+ """
555
+
556
+ completed_executions_count: int
557
+ created_at: datetime.datetime
558
+ description: Optional[str]
559
+ enqueued_by: str
560
+ execution_config: ExecutionConfig
561
+ job_id: str
562
+ job_metadata: List[JobMetadata]
563
+ name: Optional[str]
564
+ schedule: Schedule
565
+ target_config: Optional[TargetConfig]
566
+ terminated_at: Optional[datetime.datetime]
567
+
568
+ def __init__(
569
+ self,
570
+ completed_executions_count: int,
571
+ created_at: datetime.datetime,
572
+ description: Optional[str],
573
+ enqueued_by: str,
574
+ execution_config: ExecutionConfig,
575
+ job_id: str,
576
+ job_metadata: List[JobMetadata],
577
+ name: Optional[str],
578
+ schedule: Schedule,
579
+ target_config: Optional[TargetConfig],
580
+ terminated_at: Optional[datetime.datetime],
581
+ ):
582
+ self.completed_executions_count = completed_executions_count
583
+ self.created_at = created_at
584
+ self.description = description
585
+ self.enqueued_by = enqueued_by
586
+ self.execution_config = execution_config
587
+ self.job_id = job_id
588
+ self.job_metadata = job_metadata
589
+ self.name = name
590
+ self.schedule = schedule
591
+ self.target_config = target_config
592
+ self.terminated_at = terminated_at
593
+ self._manager: Optional[JobsManager] = None
594
+
595
+ @classmethod
596
+ def from_dict(cls, obj: Dict[str, Any], manager: 'JobsManager') -> 'Job':
597
+ """
598
+ Construct a Job from a dictionary of values.
599
+
600
+ Parameters
601
+ ----------
602
+ obj : dict
603
+ Dictionary of values
604
+
605
+ Returns
606
+ -------
607
+ :class:`Job`
608
+
609
+ """
610
+ target_config = obj.get('targetConfig')
611
+ if target_config is not None:
612
+ target_config = TargetConfig.from_dict(target_config)
613
+
614
+ out = cls(
615
+ completed_executions_count=obj['completedExecutionsCount'],
616
+ created_at=to_datetime_strict(obj['createdAt']),
617
+ description=obj.get('description'),
618
+ enqueued_by=obj['enqueuedBy'],
619
+ execution_config=ExecutionConfig.from_dict(obj['executionConfig']),
620
+ job_id=obj['jobID'],
621
+ job_metadata=[JobMetadata.from_dict(x) for x in obj['jobMetadata']],
622
+ name=obj.get('name'),
623
+ schedule=Schedule.from_dict(obj['schedule']),
624
+ target_config=target_config,
625
+ terminated_at=to_datetime(obj.get('terminatedAt')),
626
+ )
627
+ out._manager = manager
628
+ return out
629
+
630
+ def wait(self, timeout: Optional[int] = None) -> bool:
631
+ """Wait for the job to complete."""
632
+ if self._manager is None:
633
+ raise ManagementError(msg='Job not initialized with JobsManager')
634
+ return self._manager._wait_for_job(self, timeout)
635
+
636
+ def get_executions(
637
+ self,
638
+ start_execution_number: int,
639
+ end_execution_number: int,
640
+ ) -> ExecutionsData:
641
+ """Get executions for the job."""
642
+ if self._manager is None:
643
+ raise ManagementError(msg='Job not initialized with JobsManager')
644
+ return self._manager.get_executions(
645
+ self.job_id,
646
+ start_execution_number,
647
+ end_execution_number,
648
+ )
649
+
650
+ def get_parameters(self) -> List[Parameter]:
651
+ """Get parameters for the job."""
652
+ if self._manager is None:
653
+ raise ManagementError(msg='Job not initialized with JobsManager')
654
+ return self._manager.get_parameters(self.job_id)
655
+
656
+ def delete(self) -> bool:
657
+ """Delete the job."""
658
+ if self._manager is None:
659
+ raise ManagementError(msg='Job not initialized with JobsManager')
660
+ return self._manager.delete(self.job_id)
661
+
662
+ def __str__(self) -> str:
663
+ """Return string representation."""
664
+ return vars_to_str(self)
665
+
666
+ def __repr__(self) -> str:
667
+ """Return string representation."""
668
+ return str(self)
669
+
670
+
671
+ class JobsManager(object):
672
+ """
673
+ SingleStoreDB scheduled notebook jobs manager.
674
+
675
+ This class should be instantiated using :attr:`Organization.jobs`.
676
+
677
+ Parameters
678
+ ----------
679
+ manager : WorkspaceManager, optional
680
+ The WorkspaceManager the JobsManager belongs to
681
+
682
+ See Also
683
+ --------
684
+ :attr:`Organization.jobs`
685
+ """
686
+
687
+ def __init__(self, manager: Optional[Manager]):
688
+ self._manager = manager
689
+
690
+ def schedule(
691
+ self,
692
+ notebook_path: str,
693
+ mode: Mode,
694
+ create_snapshot: bool,
695
+ name: Optional[str] = None,
696
+ description: Optional[str] = None,
697
+ execution_interval_in_minutes: Optional[int] = None,
698
+ start_at: Optional[datetime.datetime] = None,
699
+ runtime_name: Optional[str] = None,
700
+ resume_target: Optional[bool] = None,
701
+ parameters: Optional[Dict[str, Any]] = None,
702
+ ) -> Job:
703
+ """Creates and returns a scheduled notebook job."""
704
+ if self._manager is None:
705
+ raise ManagementError(msg='JobsManager not initialized')
706
+
707
+ schedule = dict(
708
+ mode=mode.value,
709
+ ) # type: Dict[str, Any]
710
+
711
+ if start_at is not None:
712
+ schedule['startAt'] = from_datetime(start_at)
713
+
714
+ if execution_interval_in_minutes is not None:
715
+ schedule['executionIntervalInMinutes'] = execution_interval_in_minutes
716
+
717
+ execution_config = dict(
718
+ createSnapshot=create_snapshot,
719
+ notebookPath=notebook_path,
720
+ ) # type: Dict[str, Any]
721
+
722
+ if runtime_name is not None:
723
+ execution_config['runtimeName'] = runtime_name
724
+
725
+ target_config = None # type: Optional[Dict[str, Any]]
726
+ database_name = get_database_name()
727
+ if database_name is not None:
728
+ target_config = dict(
729
+ databaseName=database_name,
730
+ )
731
+
732
+ if resume_target is not None:
733
+ target_config['resumeTarget'] = resume_target
734
+
735
+ workspace_id = get_workspace_id()
736
+ virtual_workspace_id = get_virtual_workspace_id()
737
+ cluster_id = get_cluster_id()
738
+ if virtual_workspace_id is not None:
739
+ target_config['targetID'] = virtual_workspace_id
740
+ target_config['targetType'] = TargetType.VIRTUAL_WORKSPACE.value
741
+
742
+ elif workspace_id is not None:
743
+ target_config['targetID'] = workspace_id
744
+ target_config['targetType'] = TargetType.WORKSPACE.value
745
+
746
+ elif cluster_id is not None:
747
+ target_config['targetID'] = cluster_id
748
+ target_config['targetType'] = TargetType.CLUSTER.value
749
+
750
+ job_run_json = dict(
751
+ schedule=schedule,
752
+ executionConfig=execution_config,
753
+ ) # type: Dict[str, Any]
754
+
755
+ if target_config is not None:
756
+ job_run_json['targetConfig'] = target_config
757
+
758
+ if name is not None:
759
+ job_run_json['name'] = name
760
+
761
+ if description is not None:
762
+ job_run_json['description'] = description
763
+
764
+ if parameters is not None:
765
+ job_run_json['parameters'] = [
766
+ dict(
767
+ name=k,
768
+ value=str(parameters[k]),
769
+ type=type_to_parameter_conversion_map[type(parameters[k])],
770
+ ) for k in parameters
771
+ ]
772
+
773
+ res = self._manager._post('jobs', json=job_run_json).json()
774
+ return Job.from_dict(res, self)
775
+
776
+ def run(
777
+ self,
778
+ notebook_path: str,
779
+ runtime_name: Optional[str] = None,
780
+ parameters: Optional[Dict[str, Any]] = None,
781
+ ) -> Job:
782
+ """Creates and returns a scheduled notebook job that runs once immediately."""
783
+ return self.schedule(
784
+ notebook_path,
785
+ Mode.ONCE,
786
+ False,
787
+ start_at=datetime.datetime.now(),
788
+ runtime_name=runtime_name,
789
+ parameters=parameters,
790
+ )
791
+
792
+ def wait(self, jobs: List[Union[str, Job]], timeout: Optional[int] = None) -> bool:
793
+ """Wait for jobs to finish executing."""
794
+ if timeout is not None:
795
+ if timeout <= 0:
796
+ return False
797
+ finish_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
798
+
799
+ for job in jobs:
800
+ if timeout is not None:
801
+ job_timeout = int((finish_time - datetime.datetime.now()).total_seconds())
802
+ else:
803
+ job_timeout = None
804
+
805
+ res = self._wait_for_job(job, job_timeout)
806
+ if not res:
807
+ return False
808
+
809
+ return True
810
+
811
+ def _wait_for_job(self, job: Union[str, Job], timeout: Optional[int] = None) -> bool:
812
+ if self._manager is None:
813
+ raise ManagementError(msg='JobsManager not initialized')
814
+
815
+ if timeout is not None:
816
+ if timeout <= 0:
817
+ return False
818
+ finish_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)
819
+
820
+ if isinstance(job, str):
821
+ job_id = job
822
+ else:
823
+ job_id = job.job_id
824
+
825
+ while True:
826
+ if timeout is not None and datetime.datetime.now() >= finish_time:
827
+ return False
828
+
829
+ res = self._manager._get(f'jobs/{job_id}').json()
830
+ job = Job.from_dict(res, self)
831
+ if job.schedule.mode == Mode.ONCE and job.completed_executions_count > 0:
832
+ return True
833
+ if job.schedule.mode == Mode.RECURRING:
834
+ raise ValueError(f'Cannot wait for recurring job {job_id}')
835
+ time.sleep(5)
836
+
837
+ def get(self, job_id: str) -> Job:
838
+ """Get a job by its ID."""
839
+ if self._manager is None:
840
+ raise ManagementError(msg='JobsManager not initialized')
841
+
842
+ res = self._manager._get(f'jobs/{job_id}').json()
843
+ return Job.from_dict(res, self)
844
+
845
+ def get_executions(
846
+ self,
847
+ job_id: str,
848
+ start_execution_number: int,
849
+ end_execution_number: int,
850
+ ) -> ExecutionsData:
851
+ """Get executions for a job by its ID."""
852
+ if self._manager is None:
853
+ raise ManagementError(msg='JobsManager not initialized')
854
+ path = (
855
+ f'jobs/{job_id}/executions'
856
+ f'?start={start_execution_number}'
857
+ f'&end={end_execution_number}'
858
+ )
859
+ res = self._manager._get(path).json()
860
+ return ExecutionsData.from_dict(res)
861
+
862
+ def get_parameters(self, job_id: str) -> List[Parameter]:
863
+ """Get parameters for a job by its ID."""
864
+ if self._manager is None:
865
+ raise ManagementError(msg='JobsManager not initialized')
866
+
867
+ res = self._manager._get(f'jobs/{job_id}/parameters').json()
868
+ return [Parameter.from_dict(p) for p in res]
869
+
870
+ def delete(self, job_id: str) -> bool:
871
+ """Delete a job by its ID."""
872
+ if self._manager is None:
873
+ raise ManagementError(msg='JobsManager not initialized')
874
+
875
+ return self._manager._delete(f'jobs/{job_id}').json()
876
+
877
+ def modes(self) -> Type[Mode]:
878
+ """Get all possible job scheduling modes."""
879
+ return Mode
880
+
881
+ def runtimes(self) -> List[Runtime]:
882
+ """Get all available job runtimes."""
883
+ if self._manager is None:
884
+ raise ManagementError(msg='JobsManager not initialized')
885
+
886
+ res = self._manager._get('jobs/runtimes').json()
887
+ return [Runtime.from_dict(r) for r in res]