From b4738897ddfa9d4c6668dd7b98c8ff383bb5c304 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Tue, 15 Jul 2025 18:55:01 +0100 Subject: [PATCH 1/3] manager v2 --- singlestoredb/management/manager.py | 46 +++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/singlestoredb/management/manager.py b/singlestoredb/management/manager.py index 9474360a8..b0711569a 100644 --- a/singlestoredb/management/manager.py +++ b/singlestoredb/management/manager.py @@ -1,8 +1,10 @@ #!/usr/bin/env python """SingleStoreDB Base Manager.""" import os +import re import sys import time +from copy import deepcopy from typing import Any from typing import Dict from typing import List @@ -42,9 +44,6 @@ def is_jwt(token: str) -> bool: class Manager(object): """SingleStoreDB manager base class.""" - #: Management API version if none is specified. - default_version = config.get_option('management.version') or 'v1' - #: Base URL if none is specified. default_base_url = config.get_option('management.base_url') \ or 'https://api.singlestore.com' @@ -53,7 +52,7 @@ class Manager(object): obj_type = '' def __init__( - self, access_token: Optional[str] = None, version: Optional[str] = None, + self, access_token: Optional[str] = None, version: Optional[str] = 'v1', base_url: Optional[str] = None, *, organization_id: Optional[str] = None, ): from .. import __version__ as client_version @@ -72,17 +71,37 @@ def __init__( 'User-Agent': f'SingleStoreDB-Python/{client_version}', }) - self._base_url = urljoin( + self._base_url = ''.join([ base_url or config.get_option('management.base_url') or type(self).default_base_url, - version or type(self).default_version, - ) + '/' - + '/', + ]) + self._version = version + self._access_token = new_access_token self._params: Dict[str, str] = {} if organization_id: self._params['organizationID'] = organization_id + def copy(self) -> 'Manager': + """Create a new instance with the same settings.""" + new_manager = type(self).__new__(type(self)) + new_manager._is_jwt = self._is_jwt + new_manager._sess = deepcopy(self._sess) + new_manager._base_url = self._base_url + new_manager._version = self._version + new_manager._access_token = self._access_token + new_manager._params = deepcopy(self._params) + return new_manager + + def __getattr__(self, name: str) -> Any: + """Handle dynamic version attributes (v2, v3, etc.).""" + if re.match(r'^v\d+[0-9a-z]*$', name): + new_mgr = self.copy() + new_mgr._version = name + return new_mgr + return super().__getattribute__(name) + def _check( self, res: requests.Response, url: str, params: Dict[str, Any], ) -> requests.Response: @@ -125,8 +144,12 @@ def _doit( # Refresh the JWT as needed if self._is_jwt: self._sess.headers.update({'Authorization': f'Bearer {get_token()}'}) + + # Combine version and path + versioned_path = f'{self._version}/{path}' + return getattr(self._sess, method.lower())( - urljoin(self._base_url, path), *args, **kwargs, + urljoin(self._base_url, versioned_path), *args, **kwargs, ) def _get(self, path: str, *args: Any, **kwargs: Any) -> requests.Response: @@ -300,3 +323,8 @@ def _wait_on_state( out = getattr(self, f'get_{self.obj_type}')(out.id) return out + + +class ManagerV2(Manager): + """V2 API implementation.""" + default_version = 'v2' From af1421d2452f4b2a321714b0a913ba9c7c0d0607 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Tue, 15 Jul 2025 19:16:46 +0100 Subject: [PATCH 2/3] use management.version when version is not passed --- singlestoredb/management/cluster.py | 2 +- singlestoredb/management/files.py | 2 +- singlestoredb/management/manager.py | 24 +++++++++++------------- singlestoredb/management/region.py | 2 +- singlestoredb/management/workspace.py | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/singlestoredb/management/cluster.py b/singlestoredb/management/cluster.py index 35428abd9..89324c4af 100644 --- a/singlestoredb/management/cluster.py +++ b/singlestoredb/management/cluster.py @@ -440,7 +440,7 @@ def manage_cluster( access_token : str, optional The API key or other access token for the cluster management API version : str, optional - Version of the API to use + Version of the API to use (default: 'v1') base_url : str, optional Base URL of the cluster management API organization_id: str, optional diff --git a/singlestoredb/management/files.py b/singlestoredb/management/files.py index ac3561ad7..330c077ca 100644 --- a/singlestoredb/management/files.py +++ b/singlestoredb/management/files.py @@ -534,7 +534,7 @@ def manage_files( access_token : str, optional The API key or other access token for the files management API version : str, optional - Version of the API to use + Version of the API to use (default: 'v1') base_url : str, optional Base URL of the files management API organization_id : str, optional diff --git a/singlestoredb/management/manager.py b/singlestoredb/management/manager.py index b0711569a..4e68bda36 100644 --- a/singlestoredb/management/manager.py +++ b/singlestoredb/management/manager.py @@ -44,6 +44,9 @@ def is_jwt(token: str) -> bool: class Manager(object): """SingleStoreDB manager base class.""" + #: Management API version if none is specified. + default_version = config.get_option('management.version') or 'v1' + #: Base URL if none is specified. default_base_url = config.get_option('management.base_url') \ or 'https://api.singlestore.com' @@ -52,7 +55,7 @@ class Manager(object): obj_type = '' def __init__( - self, access_token: Optional[str] = None, version: Optional[str] = 'v1', + self, access_token: Optional[str] = None, version: Optional[str] = None, base_url: Optional[str] = None, *, organization_id: Optional[str] = None, ): from .. import __version__ as client_version @@ -62,6 +65,8 @@ def __init__( if not new_access_token: raise ManagementError(msg='No management token was configured.') + self.version = version or self.default_version + self._is_jwt = not access_token and new_access_token and is_jwt(new_access_token) self._sess = requests.Session() self._sess.headers.update({ @@ -71,13 +76,11 @@ def __init__( 'User-Agent': f'SingleStoreDB-Python/{client_version}', }) - self._base_url = ''.join([ + self._base_url = ( base_url or config.get_option('management.base_url') - or type(self).default_base_url, - '/', - ]) - self._version = version + or type(self).default_base_url + ) + '/' self._access_token = new_access_token self._params: Dict[str, str] = {} if organization_id: @@ -89,7 +92,7 @@ def copy(self) -> 'Manager': new_manager._is_jwt = self._is_jwt new_manager._sess = deepcopy(self._sess) new_manager._base_url = self._base_url - new_manager._version = self._version + new_manager.version = self.version new_manager._access_token = self._access_token new_manager._params = deepcopy(self._params) return new_manager @@ -98,7 +101,7 @@ def __getattr__(self, name: str) -> Any: """Handle dynamic version attributes (v2, v3, etc.).""" if re.match(r'^v\d+[0-9a-z]*$', name): new_mgr = self.copy() - new_mgr._version = name + new_mgr.version = name return new_mgr return super().__getattribute__(name) @@ -323,8 +326,3 @@ def _wait_on_state( out = getattr(self, f'get_{self.obj_type}')(out.id) return out - - -class ManagerV2(Manager): - """V2 API implementation.""" - default_version = 'v2' diff --git a/singlestoredb/management/region.py b/singlestoredb/management/region.py index 546070e4b..84b206a1e 100644 --- a/singlestoredb/management/region.py +++ b/singlestoredb/management/region.py @@ -143,7 +143,7 @@ def manage_regions( access_token : str, optional The API key or other access token for the workspace management API version : str, optional - Version of the API to use + Version of the API to use (default: 'v1') base_url : str, optional Base URL of the workspace management API diff --git a/singlestoredb/management/workspace.py b/singlestoredb/management/workspace.py index 7b3c08cc2..e16d6ea12 100644 --- a/singlestoredb/management/workspace.py +++ b/singlestoredb/management/workspace.py @@ -1978,7 +1978,7 @@ def manage_workspaces( access_token : str, optional The API key or other access token for the workspace management API version : str, optional - Version of the API to use + Version of the API to use (default is 'v1') base_url : str, optional Base URL of the workspace management API organization_id : str, optional From de52635ccda57d80a85bc63486990bb2d936a2b9 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Wed, 16 Jul 2025 03:47:42 +0100 Subject: [PATCH 3/3] fix version attr --- singlestoredb/management/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/singlestoredb/management/manager.py b/singlestoredb/management/manager.py index 4e68bda36..4f8bb85c2 100644 --- a/singlestoredb/management/manager.py +++ b/singlestoredb/management/manager.py @@ -149,7 +149,7 @@ def _doit( self._sess.headers.update({'Authorization': f'Bearer {get_token()}'}) # Combine version and path - versioned_path = f'{self._version}/{path}' + versioned_path = f'{self.version}/{path}' return getattr(self._sess, method.lower())( urljoin(self._base_url, versioned_path), *args, **kwargs,