Skip to content

Commit d790ff0

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

File tree

2 files changed

+359
-1
lines changed

2 files changed

+359
-1
lines changed

singlestoredb/management/workspace.py

Lines changed: 244 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1307,18 +1307,29 @@ class StarterWorkspace(object):
13071307

13081308
name: str
13091309
id: str
1310+
database_name: str
1311+
endpoint: Optional[str]
13101312

13111313
def __init__(
13121314
self,
13131315
name: str,
13141316
id: str,
1317+
database_name: str,
1318+
endpoint: Optional[str] = None,
13151319
):
13161320
#: Name of the starter workspace
13171321
self.name = name
13181322

13191323
#: Unique ID of the starter workspace
13201324
self.id = id
13211325

1326+
#: Name of the database associated with the starter workspace
1327+
self.database_name = database_name
1328+
1329+
#: Endpoint to connect to the starter workspace. The endpoint is in the form
1330+
#: of ``hostname:port``
1331+
self.endpoint = endpoint
1332+
13221333
self._manager: Optional[WorkspaceManager] = None
13231334

13241335
def __str__(self) -> str:
@@ -1351,10 +1362,56 @@ def from_dict(
13511362
out = cls(
13521363
name=obj['name'],
13531364
id=obj['virtualWorkspaceID'],
1365+
database_name=obj['databaseName'],
1366+
endpoint=obj.get('endpoint'),
13541367
)
13551368
out._manager = manager
13561369
return out
13571370

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

13771434
@property
1378-
def starter_workspaces(self) -> NamedList[StarterWorkspace]:
1435+
def starter_workspaces(self) -> NamedList['StarterWorkspace']:
13791436
"""Return a list of available starter workspaces."""
13801437
if self._manager is None:
13811438
raise ManagementError(
@@ -1386,6 +1443,67 @@ def starter_workspaces(self) -> NamedList[StarterWorkspace]:
13861443
[StarterWorkspace.from_dict(item, self._manager) for item in res.json()],
13871444
)
13881445

1446+
def create_user(
1447+
self,
1448+
user_name: str,
1449+
password: Optional[str] = None,
1450+
) -> Dict[str, str]:
1451+
"""
1452+
Create a new user for this starter workspace.
1453+
1454+
Parameters
1455+
----------
1456+
user_name : str
1457+
The starter workspace user name to connect the new user to the database
1458+
password : str, optional
1459+
Password for the new user. If not provided, a password will be
1460+
auto-generated by the system.
1461+
1462+
Returns
1463+
-------
1464+
Dict[str, str]
1465+
Dictionary containing 'userID' and 'password' of the created user
1466+
1467+
Raises
1468+
------
1469+
ManagementError
1470+
If no workspace manager is associated with this object.
1471+
"""
1472+
if self._manager is None:
1473+
raise ManagementError(
1474+
msg='No workspace manager is associated with this object.',
1475+
)
1476+
1477+
return self._manager.create_starter_workspace_user(self.id, user_name, password)
1478+
1479+
@classmethod
1480+
def create_starter_workspace(
1481+
cls,
1482+
manager: 'WorkspaceManager',
1483+
name: str,
1484+
database_name: str,
1485+
workspace_group: dict[str, str],
1486+
) -> 'StarterWorkspace':
1487+
"""
1488+
Create a new starter (shared tier) workspace.
1489+
1490+
Parameters
1491+
----------
1492+
manager : WorkspaceManager
1493+
The WorkspaceManager instance to use for the API call
1494+
name : str
1495+
Name of the starter workspace
1496+
database_name : str
1497+
Name of the database for the starter workspace
1498+
workspace_group : dict[str, str]
1499+
Workspace group input (dict with keys: 'name', 'cell_id')
1500+
1501+
Returns
1502+
-------
1503+
:class:`StarterWorkspace`
1504+
"""
1505+
return manager.create_starter_workspace(name, database_name, workspace_group)
1506+
13891507

13901508
class Billing(object):
13911509
"""Billing information."""
@@ -1717,6 +1835,131 @@ def get_starter_workspace(self, id: str) -> StarterWorkspace:
17171835
res = self._get(f'sharedtier/virtualWorkspaces/{id}')
17181836
return StarterWorkspace.from_dict(res.json(), manager=self)
17191837

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

17211964
def manage_workspaces(
17221965
access_token: Optional[str] = None,

0 commit comments

Comments
 (0)