Skip to content

Commit 60ad5ec

Browse files
author
Pedro Rodrigues
committed
free tier endpoints
1 parent 45cf138 commit 60ad5ec

File tree

2 files changed

+362
-1
lines changed

2 files changed

+362
-1
lines changed

singlestoredb/management/workspace.py

Lines changed: 247 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1301,24 +1301,38 @@ class StarterWorkspace(object):
13011301
See Also
13021302
--------
13031303
:meth:`WorkspaceManager.get_starter_workspace`
1304+
:meth:`WorkspaceManager.create_starter_workspace`
1305+
:meth:`WorkspaceManager.terminate_starter_workspace`
1306+
:meth:`WorkspaceManager.create_starter_workspace_user`
13041307
:attr:`WorkspaceManager.starter_workspaces`
13051308
13061309
"""
13071310

13081311
name: str
13091312
id: str
1313+
database_name: str
1314+
endpoint: Optional[str]
13101315

13111316
def __init__(
13121317
self,
13131318
name: str,
13141319
id: str,
1320+
database_name: str,
1321+
endpoint: Optional[str] = None,
13151322
):
13161323
#: Name of the starter workspace
13171324
self.name = name
13181325

13191326
#: Unique ID of the starter workspace
13201327
self.id = id
13211328

1329+
#: Name of the database associated with the starter workspace
1330+
self.database_name = database_name
1331+
1332+
#: Endpoint to connect to the starter workspace. The endpoint is in the form
1333+
#: of ``hostname:port``
1334+
self.endpoint = endpoint
1335+
13221336
self._manager: Optional[WorkspaceManager] = None
13231337

13241338
def __str__(self) -> str:
@@ -1351,10 +1365,56 @@ def from_dict(
13511365
out = cls(
13521366
name=obj['name'],
13531367
id=obj['virtualWorkspaceID'],
1368+
database_name=obj['databaseName'],
1369+
endpoint=obj.get('endpoint'),
13541370
)
13551371
out._manager = manager
13561372
return out
13571373

1374+
def connect(self, **kwargs: Any) -> connection.Connection:
1375+
"""
1376+
Create a connection to the database server for this starter workspace.
1377+
1378+
Parameters
1379+
----------
1380+
**kwargs : keyword-arguments, optional
1381+
Parameters to the SingleStoreDB `connect` function except host
1382+
and port which are supplied by the starter workspace object
1383+
1384+
Returns
1385+
-------
1386+
:class:`Connection`
1387+
1388+
"""
1389+
if not self.endpoint:
1390+
raise ManagementError(
1391+
msg='An endpoint has not been set in this '
1392+
'starter workspace configuration',
1393+
)
1394+
# Parse endpoint as host:port
1395+
if ':' in self.endpoint:
1396+
host, port = self.endpoint.split(':', 1)
1397+
kwargs['host'] = host
1398+
kwargs['port'] = int(port)
1399+
else:
1400+
kwargs['host'] = self.endpoint
1401+
return connection.connect(**kwargs)
1402+
1403+
def terminate(self) -> None:
1404+
"""
1405+
Terminate the starter workspace.
1406+
1407+
Raises
1408+
------
1409+
ManagementError
1410+
If no workspace manager is associated with this object.
1411+
"""
1412+
if self._manager is None:
1413+
raise ManagementError(
1414+
msg='No workspace manager is associated with this object.',
1415+
)
1416+
self._manager.terminate_starter_workspace(self.id)
1417+
13581418
@property
13591419
def organization(self) -> Organization:
13601420
if self._manager is None:
@@ -1375,7 +1435,7 @@ def stage(self) -> Stage:
13751435
stages = stage
13761436

13771437
@property
1378-
def starter_workspaces(self) -> NamedList[StarterWorkspace]:
1438+
def starter_workspaces(self) -> NamedList['StarterWorkspace']:
13791439
"""Return a list of available starter workspaces."""
13801440
if self._manager is None:
13811441
raise ManagementError(
@@ -1386,6 +1446,67 @@ def starter_workspaces(self) -> NamedList[StarterWorkspace]:
13861446
[StarterWorkspace.from_dict(item, self._manager) for item in res.json()],
13871447
)
13881448

1449+
def create_user(
1450+
self,
1451+
user_name: str,
1452+
password: Optional[str] = None,
1453+
) -> Dict[str, str]:
1454+
"""
1455+
Create a new user for this starter workspace.
1456+
1457+
Parameters
1458+
----------
1459+
user_name : str
1460+
The starter workspace user name to connect the new user to the database
1461+
password : str, optional
1462+
Password for the new user. If not provided, a password will be
1463+
auto-generated by the system.
1464+
1465+
Returns
1466+
-------
1467+
Dict[str, str]
1468+
Dictionary containing 'userID' and 'password' of the created user
1469+
1470+
Raises
1471+
------
1472+
ManagementError
1473+
If no workspace manager is associated with this object.
1474+
"""
1475+
if self._manager is None:
1476+
raise ManagementError(
1477+
msg='No workspace manager is associated with this object.',
1478+
)
1479+
1480+
return self._manager.create_starter_workspace_user(self.id, user_name, password)
1481+
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.
1492+
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: 'name', 'cell_id')
1503+
1504+
Returns
1505+
-------
1506+
:class:`StarterWorkspace`
1507+
"""
1508+
return manager.create_starter_workspace(name, database_name, workspace_group)
1509+
13891510

13901511
class Billing(object):
13911512
"""Billing information."""
@@ -1717,6 +1838,131 @@ def get_starter_workspace(self, id: str) -> StarterWorkspace:
17171838
res = self._get(f'sharedtier/virtualWorkspaces/{id}')
17181839
return StarterWorkspace.from_dict(res.json(), manager=self)
17191840

1841+
def create_starter_workspace(
1842+
self,
1843+
name: str,
1844+
database_name: str,
1845+
workspace_group: dict[str, str],
1846+
) -> 'StarterWorkspace':
1847+
"""
1848+
Create a new starter (shared tier) workspace.
1849+
1850+
Parameters
1851+
----------
1852+
name : str
1853+
Name of the starter workspace
1854+
database_name : str
1855+
Name of the database for the starter workspace
1856+
workspace_group : dict[str, str]
1857+
Workspace group input (dict with keys: 'name', 'cell_id')
1858+
1859+
Returns
1860+
-------
1861+
:class:`StarterWorkspace`
1862+
"""
1863+
if not workspace_group or not isinstance(workspace_group, dict):
1864+
raise ValueError(
1865+
'workspace_group must be a dict with keys: '
1866+
"'name', 'cell_id'",
1867+
)
1868+
if set(workspace_group.keys()) != {'name', 'cell_id'}:
1869+
raise ValueError("workspace_group must contain only 'name' and 'cell_id'")
1870+
1871+
payload = {
1872+
'name': name,
1873+
'databaseName': database_name,
1874+
'workspaceGroup': {
1875+
'name': workspace_group['name'],
1876+
'cellID': workspace_group['cell_id'],
1877+
},
1878+
}
1879+
1880+
res = self._post('sharedtier/virtualWorkspaces', json=payload)
1881+
virtual_workspace_id = res.json().get('virtualWorkspaceID')
1882+
if not virtual_workspace_id:
1883+
raise ManagementError(msg='No virtualWorkspaceID returned from API')
1884+
1885+
res = self._get(f'sharedtier/virtualWorkspaces/{virtual_workspace_id}')
1886+
return StarterWorkspace.from_dict(res.json(), self)
1887+
1888+
def terminate_starter_workspace(
1889+
self,
1890+
id: str,
1891+
) -> None:
1892+
"""
1893+
Terminate a starter (shared tier) workspace.
1894+
1895+
Parameters
1896+
----------
1897+
id : str
1898+
ID of the starter workspace
1899+
wait_on_terminated : bool, optional
1900+
Wait for the starter workspace to go into 'Terminated' mode before returning
1901+
wait_interval : int, optional
1902+
Number of seconds between each server check
1903+
wait_timeout : int, optional
1904+
Total number of seconds to check server before giving up
1905+
1906+
Raises
1907+
------
1908+
ManagementError
1909+
If timeout is reached
1910+
1911+
"""
1912+
self._delete(f'sharedtier/virtualWorkspaces/{id}')
1913+
1914+
def create_starter_workspace_user(
1915+
self,
1916+
starter_workspace_id: str,
1917+
username: str,
1918+
password: Optional[str] = None,
1919+
) -> Dict[str, str]:
1920+
"""
1921+
Create a new user for a starter workspace.
1922+
1923+
Parameters
1924+
----------
1925+
starter_workspace_id : str
1926+
ID of the starter workspace
1927+
user_name : str
1928+
The starter workspace user name to connect the new user to the database
1929+
password : str, optional
1930+
Password for the new user. If not provided, a password will be
1931+
auto-generated by the system.
1932+
1933+
Returns
1934+
-------
1935+
Dict[str, str]
1936+
Dictionary containing 'userID' and 'password' of the created user
1937+
1938+
"""
1939+
payload = {
1940+
'userName': username,
1941+
}
1942+
if password is not None:
1943+
payload['password'] = password
1944+
1945+
res = self._post(
1946+
f'sharedtier/virtualWorkspaces/{starter_workspace_id}/users',
1947+
json=payload,
1948+
)
1949+
1950+
response_data = res.json()
1951+
user_id = response_data.get('userID')
1952+
if not user_id:
1953+
raise ManagementError(msg='No userID returned from API')
1954+
1955+
# Return the password provided by user or generated by API
1956+
returned_password = password if password is not None \
1957+
else response_data.get('password')
1958+
if not returned_password:
1959+
raise ManagementError(msg='No password available from API response')
1960+
1961+
return {
1962+
'userID': user_id,
1963+
'password': returned_password,
1964+
}
1965+
17201966

17211967
def manage_workspaces(
17221968
access_token: Optional[str] = None,

0 commit comments

Comments
 (0)