diff --git a/README.md b/README.md index bb9b9c6..b8db099 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,13 @@ Below is a list of the available functionality in the SDK. Using the SDK you can Database Management: -- `create_database(database_name: str, alias: str)`: Creates a new database. +- `create_database(platform: str, alias: str)`: Creates a new database. - `delete_database(database_name: str)`: Deletes to a database. - `list_databases()`: Lists all databases. -- `connect_database(database_id: str)`: Connects to a database and turns into SQL connection. Tenant Management: -- `create_tenant(tenant_name: str, alias: str, database_id: str = "")`: Creates a new tenant. +- `create_tenant(tenant_name: str, isolation_level: str, platform: str, alias: str, database_id: str = "")`: Creates a new tenant. - `delete_tenant(tenant_name: str)`: Deletes a tenant. - `list_tenants()`: Lists all tenants. - `connect_tenant(tenant_name: str)`: Connects to a tenant and turns into SQL connection. diff --git a/examples/test.py b/examples/test.py index f8524f9..8acde39 100644 --- a/examples/test.py +++ b/examples/test.py @@ -9,7 +9,10 @@ # Create a database database_id = None try: - database_id = fortress.create_database(alias="Client 1") + database_id = fortress.create_database( + platform="aws", + alias="Client 1", + ) except Exception as e: print(f"Database creation failed: {e}") @@ -17,6 +20,8 @@ try: fortress.create_tenant( tenant_id="client1", + isolation_level="dedicated", + platform="aws", alias="Client 1", database_id=database_id, ) diff --git a/fortress_sdk_python/client.py b/fortress_sdk_python/client.py index d15d5fb..32f3d43 100644 --- a/fortress_sdk_python/client.py +++ b/fortress_sdk_python/client.py @@ -34,13 +34,13 @@ class ConnectionDetails: class InternalError(Exception): - def __init__(self, message='Internal Server Error'): + def __init__(self, message="Internal Server Error"): self.message = message super().__init__(self.message) class ValidationError(Exception): - def __init__(self, message='Validation Error'): + def __init__(self, message="Validation Error"): self.message = message super().__init__(self.message) @@ -57,62 +57,52 @@ def __init__( self.api_key = api_key def get_uri(self, id: str, type: str) -> ConnectionDetails: - if type != 'tenant' and type != 'database': + if type != "tenant" and type != "database": raise ValidationError("Type must be either 'tenant' or 'database'") - endpoint = ( - f'{self.base_url}/v1/organization/{self.org_id}/{type}/{id}/uri' - ) + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/{type}/{id}/uri" response = requests.get( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) if response.status_code == 500: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) connection_details_str = None try: connection_details_str = decrypt( private_key=self.api_key, - ciphertext=json_response.get('connectionDetails', ''), + ciphertext=json_response.get("connectionDetails", ""), ) except Exception as e: raise InternalError( - 'An error occured: Unable to decrypt connection details' + "An error occured: Unable to decrypt connection details" ) if connection_details_str is None: raise InternalError( - 'An error occured: Unable to decrypt connection details' + "An error occured: Unable to decrypt connection details" ) connection_details = json.loads(connection_details_str) - url = connection_details.get('url', '') - database = connection_details.get('database', '') - port = int(connection_details.get('port', 0)) - username = connection_details.get('username', '') - password = connection_details.get('password', '') - - if ( - url == '' - or port == 0 - or username == '' - or password == '' - or database == '' - ): - raise InternalError('An error occured: Invalid connection details') + url = connection_details.get("url", "") + database = connection_details.get("database", "") + port = int(connection_details.get("port", 0)) + username = connection_details.get("username", "") + password = connection_details.get("password", "") + + if url == "" or port == 0 or username == "" or password == "" or database == "": + raise InternalError("An error occured: Invalid connection details") return ConnectionDetails( - database_id=json_response['databaseId'], + database_id=json_response["databaseId"], url=url, database=database, port=port, @@ -120,155 +110,146 @@ def get_uri(self, id: str, type: str) -> ConnectionDetails: password=password, ) - def create_database(self, alias: str = '') -> str: - endpoint = f'{self.base_url}/v1/organization/{self.org_id}/database' - payload = {'alias': alias} + def create_database(self, platform: str, alias: str = "") -> str: + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/database" + payload = {"platform": platform, "alias": alias} response = requests.post( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, json=payload, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) if response.status_code == 500: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) - return json_response['databaseId'] + return json_response["databaseId"] def delete_database(self, database_id: str): - endpoint = f'{self.base_url}/v1/organization/{self.org_id}/database/{database_id}' + endpoint = ( + f"{self.base_url}/v1/organization/{self.org_id}/database/{database_id}" + ) response = requests.delete( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) if response.status_code == 500: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) def list_databases(self) -> list[Database]: - endpoint = f'{self.base_url}/v1/organization/{self.org_id}/databases' + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/databases" response = requests.get( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) else: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) databases = [ Database( - id=data.get('databaseId', ''), - alias=data.get('alias', ''), - size=data.get('sizeBytes', 0), - average_read_iops=data.get('averageReadIOPS', 0), - average_write_iops=data.get('averageWriteIOPS', 0), + id=data.get("databaseId", ""), + alias=data.get("alias", ""), + size=data.get("sizeBytes", 0), + average_read_iops=data.get("averageReadIOPS", 0), + average_write_iops=data.get("averageWriteIOPS", 0), created_date=datetime.datetime.fromisoformat( - data.get('createdDate', '') + data.get("createdDate", "") ), ) - for data in json_response.get('databases', []) + for data in json_response.get("databases", []) ] return databases def create_tenant( - self, tenant_id: str, alias: str = '', database_id: str = '' + self, + tenant_id: str, + isolation_level: str, + platform: str, + alias: str = "", + database_id: str = "", ) -> None: - endpoint = ( - f'{self.base_url}/v1/organization/{self.org_id}/tenant/{tenant_id}' - ) - payload = {'alias': alias} + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/tenant/{tenant_id}" + payload = {"isolation": isolation_level, "platform": platform, "alias": alias} if database_id: - payload['databaseId'] = database_id + payload["databaseId"] = database_id response = requests.post( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, json=payload, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) if response.status_code == 500: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) def delete_tenant(self, tenant_id: str) -> None: - endpoint = ( - f'{self.base_url}/v1/organization/{self.org_id}/tenant/{tenant_id}' - ) + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/tenant/{tenant_id}" response = requests.delete( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) if response.status_code == 500: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) def list_tenants(self) -> list[Tenant]: - endpoint = f'{self.base_url}/v1/organization/{self.org_id}/tenants' + endpoint = f"{self.base_url}/v1/organization/{self.org_id}/tenants" response = requests.get( endpoint, - headers={'Api-Key': self.api_key}, + headers={"Api-Key": self.api_key}, ) json_response = response.json() if response.status_code != 200: if response.status_code == 400: - raise ValidationError( - json_response.get('message', 'Validation Error') - ) + raise ValidationError(json_response.get("message", "Validation Error")) else: raise InternalError( - json_response.get('message', 'Internal Server Error') + json_response.get("message", "Internal Server Error") ) tenants = [ Tenant( - name=data.get('tenantId', ''), - alias=data.get('alias', ''), - database_id=data.get('databaseId', ''), + name=data.get("tenantId", ""), + alias=data.get("alias", ""), + database_id=data.get("databaseId", ""), created_date=datetime.datetime.fromisoformat( - data.get('createdDate', '') + data.get("createdDate", "") ), ) - for data in json_response.get('tenants', []) + for data in json_response.get("tenants", []) ] return tenants diff --git a/fortress_sdk_python/fortress.py b/fortress_sdk_python/fortress.py index 8c81bf1..a05cd8b 100644 --- a/fortress_sdk_python/fortress.py +++ b/fortress_sdk_python/fortress.py @@ -12,49 +12,27 @@ def __init__( self, org_id: str, api_key: str, - base_url: str = 'https://api.fortress.build', + base_url: str = "https://api.fortress.build", ) -> None: """Initialize the Fortress client""" if not org_id: - raise ValueError('Organization ID is required') + raise ValueError("Organization ID is required") if not api_key: - raise ValueError('API Key is required') + raise ValueError("API Key is required") self.__fortress = Client(org_id, api_key, base_url) self.__connection_cache = {} self.__tenant_to_database = {} - def connect_database(self, database_id: str) -> Connection: - """ - Connect to a database on the Fortress platform - - :param database_id: ID of the database - :return: Connection object to the database - """ - if database_id in self.__connection_cache: - return self.__connection_cache[database_id] - - response = self.__fortress.get_uri(database_id, 'database') - - connection = PostgresClient( - response.url, - response.port, - response.username, - response.password, - response.database, - ).connect() - - self.__connection_cache[database_id] = connection - return connection - - def create_database(self, alias: str = '') -> str: + def create_database(self, platform: str, alias: str = "") -> str: """ Create a new database on the Fortress platform Returns the ID of the created + :param platform: The cloud platform the database will be hosted on (aws or managed) :param alias: Alias for the database (optional) """ - return self.__fortress.create_database(alias=alias) + return self.__fortress.create_database(platform=platform, alias=alias) def delete_database(self, database_id: str) -> None: """ @@ -84,7 +62,7 @@ def connect_tenant(self, tenant_id: str) -> Connection: self.__connection_cache[self.__tenant_to_database[tenant_id]] ) - response = self.__fortress.get_uri(tenant_id, 'tenant') + response = self.__fortress.get_uri(tenant_id, "tenant") connection = PostgresClient( response.url, @@ -99,17 +77,28 @@ def connect_tenant(self, tenant_id: str) -> Connection: return connection def create_tenant( - self, tenant_id: str, alias: str = '', database_id: str = '' + self, + tenant_id: str, + isolation_level: str, + platform: str, + alias: str = "", + database_id: str = "", ) -> None: """ Create a new tenant on the Fortress platform :param tenant_id: ID of the tenant + :param isolation_level: Isolation level of the tenant (shared or dedicated) + :param platform: The cloud platform the tenant will be hosted on (aws or managed) :param alias: Alias for the tenant (optional) :param database_id: ID of the database to assign the tenant to or if not provided a database will be created (optional) """ self.__fortress.create_tenant( - tenant_id=tenant_id, alias=alias, database_id=database_id + tenant_id=tenant_id, + isolation_level=isolation_level, + platform=platform, + alias=alias, + database_id=database_id, ) def delete_tenant(self, tenant_id: str) -> None: diff --git a/fortress_sdk_python/postgres.py b/fortress_sdk_python/postgres.py index 96114d3..345edce 100644 --- a/fortress_sdk_python/postgres.py +++ b/fortress_sdk_python/postgres.py @@ -91,6 +91,6 @@ def __init__( def connect(self) -> PostgresConnection: return PostgresConnection( psycopg2.connect( - f"dbname={self.__database_name} user={self.__user} password={self.__password} host={self.__host} port={self.__port} sslmode=disable" + f"dbname={self.__database_name} user={self.__user} password={self.__password} host={self.__host} port={self.__port} sslmode=require" ) ) diff --git a/pyproject.toml b/pyproject.toml index b593d55..8786249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fortress-sdk-python" -version = "0.1.14" +version = "0.1.15" description = "Fortress Platform Python API SDK" authors = ["Fortress "] readme = "README.md"