Skip to content

Commit a998dad

Browse files
committed
Update starter workspace API
1 parent 9c649cf commit a998dad

File tree

3 files changed

+75
-110
lines changed

3 files changed

+75
-110
lines changed

docs/src/api.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,15 @@ create new ones.
256256
WorkspaceManager
257257
WorkspaceManager.organization
258258
WorkspaceManager.workspace_groups
259+
WorkspaceManager.starter_workspaces
259260
WorkspaceManager.regions
261+
WorkspaceManager.shared_tier_regions
260262
WorkspaceManager.create_workspace_group
261263
WorkspaceManager.create_workspace
264+
WorkspaceManager.create_starter_workspace
262265
WorkspaceManager.get_workspace_group
263266
WorkspaceManager.get_workspace
267+
WorkspaceManager.get_starter_workspace
264268

265269

266270
WorkspaceGroup

singlestoredb/management/workspace.py

Lines changed: 69 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,20 +1400,55 @@ def connect(self, **kwargs: Any) -> connection.Connection:
14001400
kwargs['host'] = self.endpoint
14011401
return connection.connect(**kwargs)
14021402

1403-
def terminate(self) -> None:
1403+
def terminate(
1404+
self,
1405+
wait_on_terminated: bool = False,
1406+
wait_interval: int = 10,
1407+
wait_timeout: int = 600,
1408+
) -> None:
14041409
"""
14051410
Terminate the starter workspace.
14061411
1412+
Parameters
1413+
----------
1414+
wait_on_terminated : bool, optional
1415+
Wait for the workspace to go into 'Terminated' mode before returning
1416+
wait_interval : int, optional
1417+
Number of seconds between each server check
1418+
wait_timeout : int, optional
1419+
Total number of seconds to check server before giving up
1420+
14071421
Raises
14081422
------
14091423
ManagementError
1410-
If no workspace manager is associated with this object.
1424+
If timeout is reached
1425+
14111426
"""
14121427
if self._manager is None:
14131428
raise ManagementError(
14141429
msg='No workspace manager is associated with this object.',
14151430
)
1416-
self._manager.terminate_starter_workspace(self.id)
1431+
self._manager._delete(f'sharedtier/virtualWorkspaces/{self.id}')
1432+
if wait_on_terminated:
1433+
self._manager._wait_on_state(
1434+
self._manager.get_starter_workspace(self.id),
1435+
'Terminated', interval=wait_interval, timeout=wait_timeout,
1436+
)
1437+
self.refresh()
1438+
1439+
def refresh(self) -> StarterWorkspace:
1440+
"""Update the object to the current state."""
1441+
if self._manager is None:
1442+
raise ManagementError(
1443+
msg='No workspace manager is associated with this object.',
1444+
)
1445+
new_obj = self._manager.get_starter_workspace(self.id)
1446+
for name, value in vars(new_obj).items():
1447+
if isinstance(value, Mapping):
1448+
setattr(self, name, snake_to_camel_dict(value))
1449+
else:
1450+
setattr(self, name, value)
1451+
return self
14171452

14181453
@property
14191454
def organization(self) -> Organization:
@@ -1477,35 +1512,32 @@ def create_user(
14771512
msg='No workspace manager is associated with this object.',
14781513
)
14791514

1480-
return self._manager.create_starter_workspace_user(self.id, user_name, password)
1515+
payload = {
1516+
'userName': user_name,
1517+
}
1518+
if password is not None:
1519+
payload['password'] = password
14811520

1482-
@classmethod
1483-
def create_starter_workspace(
1484-
cls,
1485-
manager: 'WorkspaceManager',
1486-
name: str,
1487-
database_name: str,
1488-
workspace_group: dict[str, str],
1489-
) -> 'StarterWorkspace':
1490-
"""
1491-
Create a new starter (shared tier) workspace.
1521+
res = self._manager._post(
1522+
f'sharedtier/virtualWorkspaces/{self.id}/users',
1523+
json=payload,
1524+
)
14921525

1493-
Parameters
1494-
----------
1495-
manager : WorkspaceManager
1496-
The WorkspaceManager instance to use for the API call
1497-
name : str
1498-
Name of the starter workspace
1499-
database_name : str
1500-
Name of the database for the starter workspace
1501-
workspace_group : dict[str, str]
1502-
Workspace group input (dict with keys: 'cell_id')
1526+
response_data = res.json()
1527+
user_id = response_data.get('userID')
1528+
if not user_id:
1529+
raise ManagementError(msg='No userID returned from API')
15031530

1504-
Returns
1505-
-------
1506-
:class:`StarterWorkspace`
1507-
"""
1508-
return manager.create_starter_workspace(name, database_name, workspace_group)
1531+
# Return the password provided by user or generated by API
1532+
returned_password = password if password is not None \
1533+
else response_data.get('password')
1534+
if not returned_password:
1535+
raise ManagementError(msg='No password available from API response')
1536+
1537+
return {
1538+
'user_id': user_id,
1539+
'password': returned_password,
1540+
}
15091541

15101542

15111543
class Billing(object):
@@ -1643,6 +1675,14 @@ def regions(self) -> NamedList[Region]:
16431675
res = self._get('regions')
16441676
return NamedList([Region.from_dict(item, self) for item in res.json()])
16451677

1678+
@ttl_property(datetime.timedelta(hours=1))
1679+
def shared_tier_regions(self) -> NamedList[Region]:
1680+
"""Return a list of regions that support shared tier workspaces."""
1681+
res = self._get('regions/sharedtier')
1682+
return NamedList(
1683+
[Region.from_dict(item, self) for item in res.json()],
1684+
)
1685+
16461686
def create_workspace_group(
16471687
self,
16481688
name: str,
@@ -1884,84 +1924,6 @@ def create_starter_workspace(
18841924
res = self._get(f'sharedtier/virtualWorkspaces/{virtual_workspace_id}')
18851925
return StarterWorkspace.from_dict(res.json(), self)
18861926

1887-
def terminate_starter_workspace(
1888-
self,
1889-
id: str,
1890-
) -> None:
1891-
"""
1892-
Terminate a starter (shared tier) workspace.
1893-
1894-
Parameters
1895-
----------
1896-
id : str
1897-
ID of the starter workspace
1898-
wait_on_terminated : bool, optional
1899-
Wait for the starter workspace to go into 'Terminated' mode before returning
1900-
wait_interval : int, optional
1901-
Number of seconds between each server check
1902-
wait_timeout : int, optional
1903-
Total number of seconds to check server before giving up
1904-
1905-
Raises
1906-
------
1907-
ManagementError
1908-
If timeout is reached
1909-
1910-
"""
1911-
self._delete(f'sharedtier/virtualWorkspaces/{id}')
1912-
1913-
def create_starter_workspace_user(
1914-
self,
1915-
starter_workspace_id: str,
1916-
username: str,
1917-
password: Optional[str] = None,
1918-
) -> Dict[str, str]:
1919-
"""
1920-
Create a new user for a starter workspace.
1921-
1922-
Parameters
1923-
----------
1924-
starter_workspace_id : str
1925-
ID of the starter workspace
1926-
user_name : str
1927-
The starter workspace user name to connect the new user to the database
1928-
password : str, optional
1929-
Password for the new user. If not provided, a password will be
1930-
auto-generated by the system.
1931-
1932-
Returns
1933-
-------
1934-
Dict[str, str]
1935-
Dictionary containing 'userID' and 'password' of the created user
1936-
1937-
"""
1938-
payload = {
1939-
'userName': username,
1940-
}
1941-
if password is not None:
1942-
payload['password'] = password
1943-
1944-
res = self._post(
1945-
f'sharedtier/virtualWorkspaces/{starter_workspace_id}/users',
1946-
json=payload,
1947-
)
1948-
1949-
response_data = res.json()
1950-
user_id = response_data.get('userID')
1951-
if not user_id:
1952-
raise ManagementError(msg='No userID returned from API')
1953-
1954-
# Return the password provided by user or generated by API
1955-
returned_password = password if password is not None \
1956-
else response_data.get('password')
1957-
if not returned_password:
1958-
raise ManagementError(msg='No password available from API response')
1959-
1960-
return {
1961-
'user_id': user_id,
1962-
'password': returned_password,
1963-
}
1964-
19651927

19661928
def manage_workspaces(
19671929
access_token: Optional[str] = None,

singlestoredb/tests/test_management.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,15 @@ def setUpClass(cls):
404404
},
405405
)
406406

407-
cls.manager.create_starter_workspace_user(
408-
starter_workspace_id=cls.starter_workspace.id,
407+
cls.starter_workspace.create_user(
409408
username=cls.starter_username,
410409
password=cls.password,
411410
)
412411

413412
@classmethod
414413
def tearDownClass(cls):
415414
if cls.starter_workspace is not None:
416-
cls.starter_workspace.terminate(force=True)
415+
cls.starter_workspace.terminate()
417416
cls.manager = None
418417
cls.password = None
419418

0 commit comments

Comments
 (0)