singlestoredb 1.8.0__cp38-abi3-win_amd64.whl → 1.10.0__cp38-abi3-win_amd64.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.

@@ -21,6 +21,12 @@ from .. import config
21
21
  from .. import connection
22
22
  from ..exceptions import ManagementError
23
23
  from .billing_usage import BillingUsageItem
24
+ from .files import FileLocation
25
+ from .files import FilesObject
26
+ from .files import FilesObjectBytesReader
27
+ from .files import FilesObjectBytesWriter
28
+ from .files import FilesObjectTextReader
29
+ from .files import FilesObjectTextWriter
24
30
  from .manager import Manager
25
31
  from .organization import Organization
26
32
  from .region import Region
@@ -84,334 +90,17 @@ def get_workspace(
84
90
  raise RuntimeError('no workspace group specified')
85
91
 
86
92
 
87
- class StageObject(object):
88
- """
89
- Stage file / folder object.
90
-
91
- This object is not instantiated directly. It is used in the results
92
- of various operations in ``WorkspaceGroup.stage`` methods.
93
-
94
- """
95
-
96
- def __init__(
97
- self,
98
- name: str,
99
- path: str,
100
- size: int,
101
- type: str,
102
- format: str,
103
- mimetype: str,
104
- created: Optional[datetime.datetime],
105
- last_modified: Optional[datetime.datetime],
106
- writable: bool,
107
- content: Optional[List[str]] = None,
108
- ):
109
- #: Name of file / folder
110
- self.name = name
111
-
112
- if type == 'directory':
113
- path = re.sub(r'/*$', r'', str(path)) + '/'
114
-
115
- #: Path of file / folder
116
- self.path = path
117
-
118
- #: Size of the object (in bytes)
119
- self.size = size
120
-
121
- #: Data type: file or directory
122
- self.type = type
123
-
124
- #: Data format
125
- self.format = format
126
-
127
- #: Mime type
128
- self.mimetype = mimetype
129
-
130
- #: Datetime the object was created
131
- self.created_at = created
132
-
133
- #: Datetime the object was modified last
134
- self.last_modified_at = last_modified
135
-
136
- #: Is the object writable?
137
- self.writable = writable
138
-
139
- #: Contents of a directory
140
- self.content: List[str] = content or []
141
-
142
- self._stage: Optional[Stage] = None
143
-
144
- @classmethod
145
- def from_dict(
146
- cls,
147
- obj: Dict[str, Any],
148
- stage: Stage,
149
- ) -> StageObject:
150
- """
151
- Construct a StageObject from a dictionary of values.
152
-
153
- Parameters
154
- ----------
155
- obj : dict
156
- Dictionary of values
157
- stage : Stage
158
- Stage object to use as the parent
159
-
160
- Returns
161
- -------
162
- :class:`StageObject`
163
-
164
- """
165
- out = cls(
166
- name=obj['name'],
167
- path=obj['path'],
168
- size=obj['size'],
169
- type=obj['type'],
170
- format=obj['format'],
171
- mimetype=obj['mimetype'],
172
- created=to_datetime(obj.get('created')),
173
- last_modified=to_datetime(obj.get('last_modified')),
174
- writable=bool(obj['writable']),
175
- )
176
- out._stage = stage
177
- return out
178
-
179
- def __str__(self) -> str:
180
- """Return string representation."""
181
- return vars_to_str(self)
182
-
183
- def __repr__(self) -> str:
184
- """Return string representation."""
185
- return str(self)
186
-
187
- def open(
188
- self,
189
- mode: str = 'r',
190
- encoding: Optional[str] = None,
191
- ) -> Union[io.StringIO, io.BytesIO]:
192
- """
193
- Open a Stage path for reading or writing.
194
-
195
- Parameters
196
- ----------
197
- mode : str, optional
198
- The read / write mode. The following modes are supported:
199
- * 'r' open for reading (default)
200
- * 'w' open for writing, truncating the file first
201
- * 'x' create a new file and open it for writing
202
- The data type can be specified by adding one of the following:
203
- * 'b' binary mode
204
- * 't' text mode (default)
205
- encoding : str, optional
206
- The string encoding to use for text
207
-
208
- Returns
209
- -------
210
- StageObjectBytesReader - 'rb' or 'b' mode
211
- StageObjectBytesWriter - 'wb' or 'xb' mode
212
- StageObjectTextReader - 'r' or 'rt' mode
213
- StageObjectTextWriter - 'w', 'x', 'wt' or 'xt' mode
214
-
215
- """
216
- if self._stage is None:
217
- raise ManagementError(
218
- msg='No Stage object is associated with this object.',
219
- )
220
-
221
- if self.is_dir():
222
- raise IsADirectoryError(
223
- f'directories can not be read or written: {self.path}',
224
- )
225
-
226
- return self._stage.open(self.path, mode=mode, encoding=encoding)
227
-
228
- def download(
229
- self,
230
- local_path: Optional[PathLike] = None,
231
- *,
232
- overwrite: bool = False,
233
- encoding: Optional[str] = None,
234
- ) -> Optional[Union[bytes, str]]:
235
- """
236
- Download the content of a stage path.
237
-
238
- Parameters
239
- ----------
240
- local_path : Path or str
241
- Path to local file target location
242
- overwrite : bool, optional
243
- Should an existing file be overwritten if it exists?
244
- encoding : str, optional
245
- Encoding used to convert the resulting data
246
-
247
- Returns
248
- -------
249
- bytes or str or None
250
-
251
- """
252
- if self._stage is None:
253
- raise ManagementError(
254
- msg='No Stage object is associated with this object.',
255
- )
256
-
257
- return self._stage.download_file(
258
- self.path, local_path=local_path,
259
- overwrite=overwrite, encoding=encoding,
260
- )
261
-
262
- download_file = download
263
-
264
- def remove(self) -> None:
265
- """Delete the stage file."""
266
- if self._stage is None:
267
- raise ManagementError(
268
- msg='No Stage object is associated with this object.',
269
- )
270
-
271
- if self.type == 'directory':
272
- raise IsADirectoryError(
273
- f'path is a directory; use rmdir or removedirs {self.path}',
274
- )
275
-
276
- self._stage.remove(self.path)
277
-
278
- def rmdir(self) -> None:
279
- """Delete the empty stage directory."""
280
- if self._stage is None:
281
- raise ManagementError(
282
- msg='No Stage object is associated with this object.',
283
- )
284
-
285
- if self.type != 'directory':
286
- raise NotADirectoryError(
287
- f'path is not a directory: {self.path}',
288
- )
289
-
290
- self._stage.rmdir(self.path)
291
-
292
- def removedirs(self) -> None:
293
- """Delete the stage directory recursively."""
294
- if self._stage is None:
295
- raise ManagementError(
296
- msg='No Stage object is associated with this object.',
297
- )
298
-
299
- if self.type != 'directory':
300
- raise NotADirectoryError(
301
- f'path is not a directory: {self.path}',
302
- )
303
-
304
- self._stage.removedirs(self.path)
305
-
306
- def rename(self, new_path: PathLike, *, overwrite: bool = False) -> None:
307
- """
308
- Move the stage file to a new location.
309
-
310
- Parameters
311
- ----------
312
- new_path : Path or str
313
- The new location of the file
314
- overwrite : bool, optional
315
- Should path be overwritten if it already exists?
316
-
317
- """
318
- if self._stage is None:
319
- raise ManagementError(
320
- msg='No Stage object is associated with this object.',
321
- )
322
- out = self._stage.rename(self.path, new_path, overwrite=overwrite)
323
- self.name = out.name
324
- self.path = out.path
325
- return None
326
-
327
- def exists(self) -> bool:
328
- """Does the file / folder exist?"""
329
- if self._stage is None:
330
- raise ManagementError(
331
- msg='No Stage object is associated with this object.',
332
- )
333
- return self._stage.exists(self.path)
334
-
335
- def is_dir(self) -> bool:
336
- """Is the stage object a directory?"""
337
- return self.type == 'directory'
338
-
339
- def is_file(self) -> bool:
340
- """Is the stage object a file?"""
341
- return self.type != 'directory'
342
-
343
- def abspath(self) -> str:
344
- """Return the full path of the object."""
345
- return str(self.path)
346
-
347
- def basename(self) -> str:
348
- """Return the basename of the object."""
349
- return self.name
350
-
351
- def dirname(self) -> str:
352
- """Return the directory name of the object."""
353
- return re.sub(r'/*$', r'', os.path.dirname(re.sub(r'/*$', r'', self.path))) + '/'
354
-
355
- def getmtime(self) -> float:
356
- """Return the last modified datetime as a UNIX timestamp."""
357
- if self.last_modified_at is None:
358
- return 0.0
359
- return self.last_modified_at.timestamp()
360
-
361
- def getctime(self) -> float:
362
- """Return the creation datetime as a UNIX timestamp."""
363
- if self.created_at is None:
364
- return 0.0
365
- return self.created_at.timestamp()
366
-
367
-
368
- class StageObjectTextWriter(io.StringIO):
369
- """StringIO wrapper for writing to Stage."""
370
-
371
- def __init__(self, buffer: Optional[str], stage: Stage, stage_path: PathLike):
372
- self._stage = stage
373
- self._stage_path = stage_path
374
- super().__init__(buffer)
375
-
376
- def close(self) -> None:
377
- """Write the content to the stage path."""
378
- self._stage._upload(self.getvalue(), self._stage_path)
379
- super().close()
380
-
381
-
382
- class StageObjectTextReader(io.StringIO):
383
- """StringIO wrapper for reading from Stage."""
384
-
385
-
386
- class StageObjectBytesWriter(io.BytesIO):
387
- """BytesIO wrapper for writing to Stage."""
388
-
389
- def __init__(self, buffer: bytes, stage: Stage, stage_path: PathLike):
390
- self._stage = stage
391
- self._stage_path = stage_path
392
- super().__init__(buffer)
393
-
394
- def close(self) -> None:
395
- """Write the content to the stage path."""
396
- self._stage._upload(self.getvalue(), self._stage_path)
397
- super().close()
398
-
399
-
400
- class StageObjectBytesReader(io.BytesIO):
401
- """BytesIO wrapper for reading from Stage."""
402
-
403
-
404
- class Stage(object):
93
+ class Stage(FileLocation):
405
94
  """
406
95
  Stage manager.
407
96
 
408
97
  This object is not instantiated directly.
409
- It is returned by ``WorkspaceGroup.stage``.
98
+ It is returned by ``WorkspaceGroup.stage`` or ``StarterWorkspace.stage``.
410
99
 
411
100
  """
412
101
 
413
- def __init__(self, workspace_group: WorkspaceGroup, manager: WorkspaceManager):
414
- self._workspace_group = workspace_group
102
+ def __init__(self, deployment_id: str, manager: WorkspaceManager):
103
+ self._deployment_id = deployment_id
415
104
  self._manager = manager
416
105
 
417
106
  def open(
@@ -440,10 +129,10 @@ class Stage(object):
440
129
 
441
130
  Returns
442
131
  -------
443
- StageObjectBytesReader - 'rb' or 'b' mode
444
- StageObjectBytesWriter - 'wb' or 'xb' mode
445
- StageObjectTextReader - 'r' or 'rt' mode
446
- StageObjectTextWriter - 'w', 'x', 'wt' or 'xt' mode
132
+ FilesObjectBytesReader - 'rb' or 'b' mode
133
+ FilesObjectBytesWriter - 'wb' or 'xb' mode
134
+ FilesObjectTextReader - 'r' or 'rt' mode
135
+ FilesObjectTextWriter - 'w', 'x', 'wt' or 'xt' mode
447
136
 
448
137
  """
449
138
  if '+' in mode or 'a' in mode:
@@ -456,19 +145,19 @@ class Stage(object):
456
145
  raise FileExistsError(f'stage path already exists: {stage_path}')
457
146
  self.remove(stage_path)
458
147
  if 'b' in mode:
459
- return StageObjectBytesWriter(b'', self, stage_path)
460
- return StageObjectTextWriter('', self, stage_path)
148
+ return FilesObjectBytesWriter(b'', self, stage_path)
149
+ return FilesObjectTextWriter('', self, stage_path)
461
150
 
462
151
  if 'r' in mode:
463
152
  content = self.download_file(stage_path)
464
153
  if isinstance(content, bytes):
465
154
  if 'b' in mode:
466
- return StageObjectBytesReader(content)
155
+ return FilesObjectBytesReader(content)
467
156
  encoding = 'utf-8' if encoding is None else encoding
468
- return StageObjectTextReader(content.decode(encoding))
157
+ return FilesObjectTextReader(content.decode(encoding))
469
158
 
470
159
  if isinstance(content, str):
471
- return StageObjectTextReader(content)
160
+ return FilesObjectTextReader(content)
472
161
 
473
162
  raise ValueError(f'unrecognized file content type: {type(content)}')
474
163
 
@@ -480,7 +169,7 @@ class Stage(object):
480
169
  stage_path: PathLike,
481
170
  *,
482
171
  overwrite: bool = False,
483
- ) -> StageObject:
172
+ ) -> FilesObject:
484
173
  """
485
174
  Upload a local file.
486
175
 
@@ -518,7 +207,7 @@ class Stage(object):
518
207
  recursive: bool = True,
519
208
  include_root: bool = False,
520
209
  ignore: Optional[Union[PathLike, List[PathLike]]] = None,
521
- ) -> StageObject:
210
+ ) -> FilesObject:
522
211
  """
523
212
  Upload a folder recursively.
524
213
 
@@ -573,7 +262,7 @@ class Stage(object):
573
262
  stage_path: PathLike,
574
263
  *,
575
264
  overwrite: bool = False,
576
- ) -> StageObject:
265
+ ) -> FilesObject:
577
266
  """
578
267
  Upload content to a stage file.
579
268
 
@@ -593,14 +282,14 @@ class Stage(object):
593
282
  self.remove(stage_path)
594
283
 
595
284
  self._manager._put(
596
- f'stage/{self._workspace_group.id}/fs/{stage_path}',
285
+ f'stage/{self._deployment_id}/fs/{stage_path}',
597
286
  files={'file': content},
598
287
  headers={'Content-Type': None},
599
288
  )
600
289
 
601
290
  return self.info(stage_path)
602
291
 
603
- def mkdir(self, stage_path: PathLike, overwrite: bool = False) -> StageObject:
292
+ def mkdir(self, stage_path: PathLike, overwrite: bool = False) -> FilesObject:
604
293
  """
605
294
  Make a directory in the stage.
606
295
 
@@ -613,7 +302,7 @@ class Stage(object):
613
302
 
614
303
  Returns
615
304
  -------
616
- StageObject
305
+ FilesObject
617
306
 
618
307
  """
619
308
  stage_path = re.sub(r'/*$', r'', str(stage_path)) + '/'
@@ -625,7 +314,7 @@ class Stage(object):
625
314
  self.remove(stage_path)
626
315
 
627
316
  self._manager._put(
628
- f'stage/{self._workspace_group.id}/fs/{stage_path}?isFile=false',
317
+ f'stage/{self._deployment_id}/fs/{stage_path}?isFile=false',
629
318
  )
630
319
 
631
320
  return self.info(stage_path)
@@ -638,7 +327,7 @@ class Stage(object):
638
327
  new_path: PathLike,
639
328
  *,
640
329
  overwrite: bool = False,
641
- ) -> StageObject:
330
+ ) -> FilesObject:
642
331
  """
643
332
  Move the stage file to a new location.
644
333
 
@@ -668,13 +357,13 @@ class Stage(object):
668
357
  self.remove(new_path)
669
358
 
670
359
  self._manager._patch(
671
- f'stage/{self._workspace_group.id}/fs/{old_path}',
360
+ f'stage/{self._deployment_id}/fs/{old_path}',
672
361
  json=dict(newPath=new_path),
673
362
  )
674
363
 
675
364
  return self.info(new_path)
676
365
 
677
- def info(self, stage_path: PathLike) -> StageObject:
366
+ def info(self, stage_path: PathLike) -> FilesObject:
678
367
  """
679
368
  Return information about a stage location.
680
369
 
@@ -685,15 +374,15 @@ class Stage(object):
685
374
 
686
375
  Returns
687
376
  -------
688
- StageObject
377
+ FilesObject
689
378
 
690
379
  """
691
380
  res = self._manager._get(
692
- re.sub(r'/+$', r'/', f'stage/{self._workspace_group.id}/fs/{stage_path}'),
381
+ re.sub(r'/+$', r'/', f'stage/{self._deployment_id}/fs/{stage_path}'),
693
382
  params=dict(metadata=1),
694
383
  ).json()
695
384
 
696
- return StageObject.from_dict(res, self)
385
+ return FilesObject.from_dict(res, self)
697
386
 
698
387
  def exists(self, stage_path: PathLike) -> bool:
699
388
  """
@@ -772,7 +461,7 @@ class Stage(object):
772
461
 
773
462
  """
774
463
  res = self._manager._get(
775
- f'stage/{self._workspace_group.id}/fs/{stage_path}',
464
+ f'stage/{self._deployment_id}/fs/{stage_path}',
776
465
  ).json()
777
466
  if recursive:
778
467
  out = []
@@ -848,7 +537,7 @@ class Stage(object):
848
537
  raise IsADirectoryError(f'stage path is a directory: {stage_path}')
849
538
 
850
539
  out = self._manager._get(
851
- f'stage/{self._workspace_group.id}/fs/{stage_path}',
540
+ f'stage/{self._deployment_id}/fs/{stage_path}',
852
541
  ).content
853
542
 
854
543
  if local_path is not None:
@@ -912,7 +601,7 @@ class Stage(object):
912
601
  f'use rmdir or removedirs: {stage_path}',
913
602
  )
914
603
 
915
- self._manager._delete(f'stage/{self._workspace_group.id}/fs/{stage_path}')
604
+ self._manager._delete(f'stage/{self._deployment_id}/fs/{stage_path}')
916
605
 
917
606
  def removedirs(self, stage_path: PathLike) -> None:
918
607
  """
@@ -925,7 +614,7 @@ class Stage(object):
925
614
 
926
615
  """
927
616
  stage_path = re.sub(r'/*$', r'', str(stage_path)) + '/'
928
- self._manager._delete(f'stage/{self._workspace_group.id}/fs/{stage_path}')
617
+ self._manager._delete(f'stage/{self._deployment_id}/fs/{stage_path}')
929
618
 
930
619
  def rmdir(self, stage_path: PathLike) -> None:
931
620
  """
@@ -942,7 +631,7 @@ class Stage(object):
942
631
  if self.listdir(stage_path):
943
632
  raise OSError(f'stage folder is not empty, use removedirs: {stage_path}')
944
633
 
945
- self._manager._delete(f'stage/{self._workspace_group.id}/fs/{stage_path}')
634
+ self._manager._delete(f'stage/{self._deployment_id}/fs/{stage_path}')
946
635
 
947
636
  def __str__(self) -> str:
948
637
  """Return string representation."""
@@ -953,6 +642,9 @@ class Stage(object):
953
642
  return str(self)
954
643
 
955
644
 
645
+ StageObject = FilesObject # alias for backward compatibility
646
+
647
+
956
648
  class Workspace(object):
957
649
  """
958
650
  SingleStoreDB workspace definition.
@@ -1411,7 +1103,7 @@ class WorkspaceGroup(object):
1411
1103
  raise ManagementError(
1412
1104
  msg='No workspace manager is associated with this object.',
1413
1105
  )
1414
- return Stage(self, self._manager)
1106
+ return Stage(self.id, self._manager)
1415
1107
 
1416
1108
  stages = stage
1417
1109
 
@@ -1598,6 +1290,104 @@ class WorkspaceGroup(object):
1598
1290
  )
1599
1291
 
1600
1292
 
1293
+ class StarterWorkspace(object):
1294
+ """
1295
+ SingleStoreDB starter workspace definition.
1296
+
1297
+ This object is not instantiated directly. It is used in the results
1298
+ of API calls on the :class:`WorkspaceManager`. Existing starter workspaces are
1299
+ accessed by either :attr:`WorkspaceManager.starter_workspaces` or by calling
1300
+ :meth:`WorkspaceManager.get_starter_workspace`.
1301
+
1302
+ See Also
1303
+ --------
1304
+ :meth:`WorkspaceManager.get_starter_workspace`
1305
+ :attr:`WorkspaceManager.starter_workspaces`
1306
+
1307
+ """
1308
+
1309
+ name: str
1310
+ id: str
1311
+
1312
+ def __init__(
1313
+ self,
1314
+ name: str,
1315
+ id: str,
1316
+ ):
1317
+ #: Name of the starter workspace
1318
+ self.name = name
1319
+
1320
+ #: Unique ID of the starter workspace
1321
+ self.id = id
1322
+
1323
+ self._manager: Optional[WorkspaceManager] = None
1324
+
1325
+ def __str__(self) -> str:
1326
+ """Return string representation."""
1327
+ return vars_to_str(self)
1328
+
1329
+ def __repr__(self) -> str:
1330
+ """Return string representation."""
1331
+ return str(self)
1332
+
1333
+ @classmethod
1334
+ def from_dict(
1335
+ cls, obj: Dict[str, Any], manager: 'WorkspaceManager',
1336
+ ) -> 'StarterWorkspace':
1337
+ """
1338
+ Construct a StarterWorkspace from a dictionary of values.
1339
+
1340
+ Parameters
1341
+ ----------
1342
+ obj : dict
1343
+ Dictionary of values
1344
+ manager : WorkspaceManager, optional
1345
+ The WorkspaceManager the StarterWorkspace belongs to
1346
+
1347
+ Returns
1348
+ -------
1349
+ :class:`StarterWorkspace`
1350
+
1351
+ """
1352
+ out = cls(
1353
+ name=obj['name'],
1354
+ id=obj['virtualWorkspaceID'],
1355
+ )
1356
+ out._manager = manager
1357
+ return out
1358
+
1359
+ @property
1360
+ def organization(self) -> Organization:
1361
+ if self._manager is None:
1362
+ raise ManagementError(
1363
+ msg='No workspace manager is associated with this object.',
1364
+ )
1365
+ return self._manager.organization
1366
+
1367
+ @property
1368
+ def stage(self) -> Stage:
1369
+ """Stage manager."""
1370
+ if self._manager is None:
1371
+ raise ManagementError(
1372
+ msg='No workspace manager is associated with this object.',
1373
+ )
1374
+ return Stage(self.id, self._manager)
1375
+
1376
+ stages = stage
1377
+
1378
+ @property
1379
+ def starter_workspaces(self) -> NamedList[StarterWorkspace]:
1380
+ """Return a list of available starter workspaces."""
1381
+ if self._manager is None:
1382
+ raise ManagementError(
1383
+ msg='No workspace manager is associated with this object.',
1384
+ )
1385
+ res = self._manager._get('sharedtier/virtualWorkspaces')
1386
+ return NamedList(
1387
+ [StarterWorkspace.from_dict(item, self._manager) for item in res.json()],
1388
+ )
1389
+
1390
+
1601
1391
  class Billing(object):
1602
1392
  """Billing information."""
1603
1393
 
@@ -1691,10 +1481,11 @@ class WorkspaceManager(Manager):
1691
1481
  """
1692
1482
 
1693
1483
  #: Workspace management API version if none is specified.
1694
- default_version = config.get_option('management.version')
1484
+ default_version = config.get_option('management.version') or 'v1'
1695
1485
 
1696
1486
  #: Base URL if none is specified.
1697
- 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'
1698
1489
 
1699
1490
  #: Object type
1700
1491
  obj_type = 'workspace'
@@ -1705,6 +1496,12 @@ class WorkspaceManager(Manager):
1705
1496
  res = self._get('workspaceGroups')
1706
1497
  return NamedList([WorkspaceGroup.from_dict(item, self) for item in res.json()])
1707
1498
 
1499
+ @property
1500
+ def starter_workspaces(self) -> NamedList[StarterWorkspace]:
1501
+ """Return a list of available starter workspaces."""
1502
+ res = self._get('sharedtier/virtualWorkspaces')
1503
+ return NamedList([StarterWorkspace.from_dict(item, self) for item in res.json()])
1504
+
1708
1505
  @property
1709
1506
  def organizations(self) -> Organizations:
1710
1507
  """Return the organizations."""
@@ -1904,6 +1701,23 @@ class WorkspaceManager(Manager):
1904
1701
  res = self._get(f'workspaces/{id}')
1905
1702
  return Workspace.from_dict(res.json(), manager=self)
1906
1703
 
1704
+ def get_starter_workspace(self, id: str) -> StarterWorkspace:
1705
+ """
1706
+ Retrieve a starter workspace definition.
1707
+
1708
+ Parameters
1709
+ ----------
1710
+ id : str
1711
+ ID of the starter workspace
1712
+
1713
+ Returns
1714
+ -------
1715
+ :class:`StarterWorkspace`
1716
+
1717
+ """
1718
+ res = self._get(f'sharedtier/virtualWorkspaces/{id}')
1719
+ return StarterWorkspace.from_dict(res.json(), manager=self)
1720
+
1907
1721
 
1908
1722
  def manage_workspaces(
1909
1723
  access_token: Optional[str] = None,