From a369cfd08cefbf2c8adec7d48c2fdde67cbe5db8 Mon Sep 17 00:00:00 2001 From: Andrea Borghi Date: Fri, 11 Oct 2024 21:38:03 +0200 Subject: [PATCH] Add DataStores models --- geoservercloud/geoservercloud.py | 82 +++++++++++++++------ geoservercloud/models/__init__.py | 10 ++- geoservercloud/models/common.py | 10 ++- geoservercloud/models/dataStore.py | 70 ++++++++++++++++++ geoservercloud/models/dataStores.py | 30 ++++++++ tests/test_datastore.py | 40 +++++++++- tests/test_models_common.py | 69 +++++++++++++++++ tests/test_models_datastore.py | 110 ++++++++++++++++++++++++++++ tests/test_models_datastores.py | 55 ++++++++++++++ 9 files changed, 444 insertions(+), 32 deletions(-) create mode 100644 geoservercloud/models/dataStore.py create mode 100644 geoservercloud/models/dataStores.py create mode 100644 tests/test_models_common.py create mode 100644 tests/test_models_datastore.py create mode 100644 tests/test_models_datastores.py diff --git a/geoservercloud/geoservercloud.py b/geoservercloud/geoservercloud.py index 4fb070a..fe2e96b 100644 --- a/geoservercloud/geoservercloud.py +++ b/geoservercloud/geoservercloud.py @@ -10,7 +10,13 @@ from requests import Response from geoservercloud import utils -from geoservercloud.models import Workspace, Workspaces +from geoservercloud.models import ( + DataStores, + KeyDollarListDict, + PostGisDataStore, + Workspace, + Workspaces, +) from geoservercloud.services import ( AclEndpoints, GwcEndpoints, @@ -156,52 +162,72 @@ def unset_default_locale_for_service(self, workspace_name) -> None: """ self.set_default_locale_for_service(workspace_name, None) + def get_datastores(self, workspace_name: str) -> dict[str, Any]: + """ + Get all datastores for a given workspace + """ + response = self.get_request(self.rest_endpoints.datastores(workspace_name)) + return DataStores.from_response(response).datastores + def create_pg_datastore( self, workspace_name: str, - datastore: str, + datastore_name: str, pg_host: str, pg_port: int, pg_db: str, pg_user: str, pg_password: str, pg_schema: str = "public", + description: str | None = None, set_default_datastore: bool = False, ) -> Response | None: """ Create a PostGIS datastore from the DB connection parameters, or update it if it already exist. """ response: None | Response = None - payload: dict[str, dict[str, Any]] = Templates.postgis_data_store( - datastore=datastore, - pg_host=pg_host, - pg_port=pg_port, - pg_db=pg_db, - pg_user=pg_user, - pg_password=pg_password, - namespace=f"http://{workspace_name}", - pg_schema=pg_schema, + datastore = PostGisDataStore( + workspace_name, + datastore_name, + connection_parameters=KeyDollarListDict( + input_dict={ + "dbtype": "postgis", + "host": pg_host, + "port": pg_port, + "database": pg_db, + "user": pg_user, + "passwd": pg_password, + "schema": pg_schema, + "namespace": f"http://{workspace_name}", + "Expose primary keys": "true", + } + ), + data_store_type="PostGIS", + description=description, ) + payload = datastore.put_payload() + if not self.resource_exists( - self.rest_endpoints.datastore(workspace_name, datastore) + self.rest_endpoints.datastore(workspace_name, datastore_name) ): response = self.post_request( self.rest_endpoints.datastores(workspace_name), json=payload ) else: response = self.put_request( - self.rest_endpoints.datastore(workspace_name, datastore), json=payload + self.rest_endpoints.datastore(workspace_name, datastore_name), + json=payload, ) if set_default_datastore: - self.default_datastore = datastore + self.default_datastore = datastore_name return response def create_jndi_datastore( self, workspace_name: str, - datastore: str, + datastore_name: str, jndi_reference: str, pg_schema: str = "public", description: str | None = None, @@ -211,26 +237,36 @@ def create_jndi_datastore( Create a PostGIS datastore from JNDI resource, or update it if it already exist. """ response: None | Response = None - payload: dict[str, dict[str, Any]] = Templates.postgis_jndi_data_store( - datastore=datastore, - jndi_reference=jndi_reference, - namespace=f"http://{workspace_name}", - pg_schema=pg_schema, + datastore = PostGisDataStore( + workspace_name, + datastore_name, + connection_parameters=KeyDollarListDict( + input_dict={ + "dbtype": "postgis", + "jndiReferenceName": jndi_reference, + "schema": pg_schema, + "namespace": f"http://{workspace_name}", + "Expose primary keys": "true", + } + ), + data_store_type="PostGIS (JNDI)", description=description, ) + payload = datastore.put_payload() if not self.resource_exists( - self.rest_endpoints.datastore(workspace_name, datastore) + self.rest_endpoints.datastore(workspace_name, datastore_name) ): response = self.post_request( self.rest_endpoints.datastores(workspace_name), json=payload ) else: response = self.put_request( - self.rest_endpoints.datastore(workspace_name, datastore), json=payload + self.rest_endpoints.datastore(workspace_name, datastore_name), + json=payload, ) if set_default_datastore: - self.default_datastore = datastore + self.default_datastore = datastore_name return response diff --git a/geoservercloud/models/__init__.py b/geoservercloud/models/__init__.py index 25f7fef..59369c6 100644 --- a/geoservercloud/models/__init__.py +++ b/geoservercloud/models/__init__.py @@ -1,5 +1,13 @@ from .common import KeyDollarListDict +from .dataStore import PostGisDataStore +from .dataStores import DataStores from .workspace import Workspace from .workspaces import Workspaces -__all__ = ["KeyDollarListDict", "Workspaces", "Workspace"] +__all__ = [ + "DataStores", + "KeyDollarListDict", + "PostGisDataStore", + "Workspaces", + "Workspace", +] diff --git a/geoservercloud/models/common.py b/geoservercloud/models/common.py index 67f43fb..bdcae12 100644 --- a/geoservercloud/models/common.py +++ b/geoservercloud/models/common.py @@ -5,12 +5,14 @@ class KeyDollarListDict(dict): - def __init__(self, input_list=None, *args, **kwargs): + def __init__(self, input_list=None, input_dict=None, *args, **kwargs): super().__init__(*args, **kwargs) self.key_prefix = "@key" self.value_prefix = "$" if input_list: self.deserialize(input_list) + if input_dict: + self.update(input_dict) log.debug(self) def deserialize(self, input_list): @@ -34,6 +36,6 @@ def __repr__(self) -> str: def __str__(self): return json.dumps(self.serialize()) - # def update(self, other: dict): - # for key, value in other.items(): - # super().__setitem__(key, value) + def update(self, other: dict): # type: ignore + for key, value in other.items(): + super().__setitem__(key, value) diff --git a/geoservercloud/models/dataStore.py b/geoservercloud/models/dataStore.py new file mode 100644 index 0000000..fe196d2 --- /dev/null +++ b/geoservercloud/models/dataStore.py @@ -0,0 +1,70 @@ +import logging + +from . import KeyDollarListDict + +log = logging.getLogger() + + +class PostGisDataStore: + + def __init__( + self, + workspace_name: str, + data_store_name: str, + connection_parameters: KeyDollarListDict, + data_store_type: str = "PostGIS", + enabled: bool = True, + description: str | None = None, + ) -> None: + self.workspace_name = workspace_name + self.data_store_name = data_store_name + self.connection_parameters = connection_parameters + self.data_store_type = data_store_type + self.description = description + self.enabled = enabled + + @property + def name(self): + return self.data_store_name + + def post_payload(self): + payload = { + "dataStore": { + "name": self.data_store_name, + "type": self.data_store_type, + "connectionParameters": { + "entry": self.connection_parameters.serialize() + }, + } + } + if self.description: + payload["dataStore"]["description"] = self.description + if self.enabled: + payload["dataStore"]["enabled"] = self.enabled + return payload + + def put_payload(self): + payload = self.post_payload() + return payload + + @classmethod + def from_response(cls, response): + + json_data = response.json() + connection_parameters = cls.parse_connection_parameters(json_data) + return cls( + json_data.get("dataStore", {}).get("workspace", {}).get("name", None), + json_data.get("dataStore", {}).get("name", None), + connection_parameters, + json_data.get("dataStore", {}).get("type", "PostGIS"), + json_data.get("dataStore", {}).get("enabled", True), + json_data.get("dataStore", {}).get("description", None), + ) + + @classmethod + def parse_connection_parameters(cls, json_data): + return KeyDollarListDict( + json_data.get("dataStore", {}) + .get("connectionParameters", {}) + .get("entry", []) + ) diff --git a/geoservercloud/models/dataStores.py b/geoservercloud/models/dataStores.py new file mode 100644 index 0000000..c820b05 --- /dev/null +++ b/geoservercloud/models/dataStores.py @@ -0,0 +1,30 @@ +import jsonschema +import requests + +import logging +log = logging.getLogger() + + +class DataStores: + + def __init__(self, workspace_name: str, datastores: list[str] = []) -> None: + self.workspace_name = workspace_name + self._datastores = datastores + + @property + def datastores(self): + return self._datastores + + @classmethod + def from_response(cls, response): + json_data = response.json() + datastores = [] + workspace_name = json_data.get('dataStores', {}).get('workspace', {}).get('name', None) + for store in json_data.get('dataStores', {}).get('dataStore', []): + datastores.append(store['name']) + for data_store_name in datastores: + log.debug(f"Name: {data_store_name}") + return cls(workspace_name, datastores) + + def __repr__(self): + return str(self.datastores) \ No newline at end of file diff --git a/tests/test_datastore.py b/tests/test_datastore.py index 2b32a80..4fdd95d 100644 --- a/tests/test_datastore.py +++ b/tests/test_datastore.py @@ -25,6 +25,8 @@ def pg_payload() -> Generator[dict[str, dict[str, Any]], Any, None]: yield { "dataStore": { "name": STORE, + "type": "PostGIS", + "enabled": True, "connectionParameters": { "entry": [ {"@key": "dbtype", "$": "postgis"}, @@ -51,6 +53,8 @@ def jndi_payload() -> Generator[dict[str, dict[str, Any]], Any, None]: "dataStore": { "name": STORE, "description": DESCRIPTION, + "type": "PostGIS (JNDI)", + "enabled": True, "connectionParameters": { "entry": [ {"@key": "dbtype", "$": "postgis"}, @@ -73,6 +77,34 @@ def jndi_payload() -> Generator[dict[str, dict[str, Any]], Any, None]: } +@pytest.fixture(scope="module") +def datastores_response() -> Generator[dict[str, Any], Any, None]: + yield { + "dataStores": { + "dataStore": [ + { + "name": STORE, + "href": f"{GEOSERVER_URL}/rest/workspaces/{WORKSPACE}/datastores/{STORE}.json", + } + ], + } + } + + +def test_get_datastores( + geoserver: GeoServerCloud, datastores_response: dict[str, Any] +) -> None: + with responses.RequestsMock() as rsps: + rsps.get( + url=f"{GEOSERVER_URL}/rest/workspaces/{WORKSPACE}/datastores.json", + status=200, + json=datastores_response, + ) + + datastores = geoserver.get_datastores(workspace_name=WORKSPACE) + assert datastores == ["test_store"] + + def test_create_pg_datastore( geoserver: GeoServerCloud, pg_payload: dict[str, dict[str, Any]] ) -> None: @@ -90,7 +122,7 @@ def test_create_pg_datastore( response = geoserver.create_pg_datastore( workspace_name=WORKSPACE, - datastore=STORE, + datastore_name=STORE, pg_host=HOST, pg_port=PORT, pg_db=DATABASE, @@ -119,7 +151,7 @@ def test_update_pg_datastore( response = geoserver.create_pg_datastore( workspace_name=WORKSPACE, - datastore=STORE, + datastore_name=STORE, pg_host=HOST, pg_port=PORT, pg_db=DATABASE, @@ -148,7 +180,7 @@ def test_create_jndi_datastore( response = geoserver.create_jndi_datastore( workspace_name=WORKSPACE, - datastore=STORE, + datastore_name=STORE, jndi_reference=JNDI, pg_schema=SCHEMA, description=DESCRIPTION, @@ -174,7 +206,7 @@ def test_update_jndi_datastore( response = geoserver.create_jndi_datastore( workspace_name=WORKSPACE, - datastore=STORE, + datastore_name=STORE, jndi_reference=JNDI, pg_schema=SCHEMA, description=DESCRIPTION, diff --git a/tests/test_models_common.py b/tests/test_models_common.py new file mode 100644 index 0000000..59538d9 --- /dev/null +++ b/tests/test_models_common.py @@ -0,0 +1,69 @@ +import json + +import pytest + +from geoservercloud.models import ( # Adjust import based on your module location + KeyDollarListDict, +) + + +# Test initialization with input_list (deserialization) +def test_keydollarlistdict_initialization_with_input_list(): + input_list = [{"@key": "host", "$": "localhost"}, {"@key": "port", "$": "5432"}] + + kdl_dict = KeyDollarListDict(input_list) + + assert kdl_dict["host"] == "localhost" + assert kdl_dict["port"] == "5432" + + +# Test initialization without input_list +def test_keydollarlistdict_initialization_without_input_list(): + kdl_dict = KeyDollarListDict() + + assert len(kdl_dict) == 0 # Should be an empty dictionary + + +# Test deserialization of input_list +def test_keydollarlistdict_deserialization(): + input_list = [ + {"@key": "username", "$": "admin"}, + {"@key": "password", "$": "secret"}, + ] + + kdl_dict = KeyDollarListDict(input_list) + + assert kdl_dict["username"] == "admin" + assert kdl_dict["password"] == "secret" + + +# Test serialization method +def test_keydollarlistdict_serialization(): + kdl_dict = KeyDollarListDict() + kdl_dict["host"] = "localhost" + kdl_dict["port"] = "5432" + + expected_output = [ + {"@key": "host", "$": "localhost"}, + {"@key": "port", "$": "5432"}, + ] + + assert kdl_dict.serialize() == expected_output + + +# Test __repr__ method +def test_keydollarlistdict_repr(): + kdl_dict = KeyDollarListDict([{"@key": "db", "$": "postgres"}]) + + expected_repr = "[{'@key': 'db', '$': 'postgres'}]" + + assert repr(kdl_dict) == expected_repr + + +# Test __str__ method +def test_keydollarlistdict_str(): + kdl_dict = KeyDollarListDict([{"@key": "db", "$": "postgres"}]) + + expected_str = json.dumps([{"@key": "db", "$": "postgres"}]) + + assert str(kdl_dict) == expected_str diff --git a/tests/test_models_datastore.py b/tests/test_models_datastore.py new file mode 100644 index 0000000..b03b101 --- /dev/null +++ b/tests/test_models_datastore.py @@ -0,0 +1,110 @@ +from unittest.mock import Mock + +import pytest + +from geoservercloud.models import ( # Adjust based on your actual module name + KeyDollarListDict, + PostGisDataStore, +) + + +# Test initialization of the PostGisDataStore class +def test_postgisdatastore_initialization(): + connection_parameters = KeyDollarListDict( + [{"@key": "host", "$": "localhost"}, {"@key": "port", "$": "5432"}] + ) + + datastore = PostGisDataStore( + "test_workspace", "test_datastore", connection_parameters + ) + + assert datastore.workspace_name == "test_workspace" + assert datastore.data_store_name == "test_datastore" + assert datastore.connection_parameters == connection_parameters + assert datastore.data_store_type == "PostGIS" + + +# Test put_payload method +def test_postgisdatastore_put_payload(): + connection_parameters = KeyDollarListDict( + [{"@key": "host", "$": "localhost"}, {"@key": "port", "$": "5432"}] + ) + + datastore = PostGisDataStore( + "test_workspace", "test_datastore", connection_parameters + ) + + expected_payload = { + "dataStore": { + "name": "test_datastore", + "type": "PostGIS", + "enabled": True, + "connectionParameters": { + "entry": [ + {"@key": "host", "$": "localhost"}, + {"@key": "port", "$": "5432"}, + ] + }, + } + } + + assert datastore.put_payload() == expected_payload + + +# Test post_payload method (should return the same as put_payload) +def test_postgisdatastore_post_payload(): + connection_parameters = KeyDollarListDict( + [{"@key": "host", "$": "localhost"}, {"@key": "port", "$": "5432"}] + ) + + datastore = PostGisDataStore( + "test_workspace", "test_datastore", connection_parameters + ) + + assert datastore.post_payload() == datastore.put_payload() + + +# Test from_response class method +def test_postgisdatastore_from_response(mocker): + mock_response = Mock() + mock_response.json.return_value = { + "dataStore": { + "name": "test_datastore", + "type": "PostGIS", + "connectionParameters": { + "entry": [ + {"@key": "host", "$": "localhost"}, + {"@key": "port", "$": "5432"}, + ] + }, + } + } + + datastore = PostGisDataStore.from_response(mock_response) + + assert datastore.data_store_name == "test_datastore" + assert datastore.data_store_type == "PostGIS" + + # Check that connection parameters were correctly parsed into a KeyDollarListDict + assert datastore.connection_parameters["host"] == "localhost" + assert datastore.connection_parameters["port"] == "5432" + + +# Test parse_connection_parameters method +def test_postgisdatastore_parse_connection_parameters(): + json_data = { + "dataStore": { + "connectionParameters": { + "entry": [ + {"@key": "host", "$": "localhost"}, + {"@key": "port", "$": "5432"}, + ] + } + } + } + + connection_params = PostGisDataStore.parse_connection_parameters(json_data) + + assert isinstance(connection_params, KeyDollarListDict) + assert connection_params["host"] == "localhost" + assert connection_params["port"] == "5432" diff --git a/tests/test_models_datastores.py b/tests/test_models_datastores.py new file mode 100644 index 0000000..bcb3caa --- /dev/null +++ b/tests/test_models_datastores.py @@ -0,0 +1,55 @@ +import pytest +from unittest.mock import Mock +from geoservercloud.models import DataStores # Replace with the actual module name + +# Test initialization of DataStores class +def test_datastores_initialization(): + workspace_name = "test_workspace" + datastores = ["store1", "store2"] + + ds = DataStores(workspace_name, datastores) + + assert ds.workspace_name == "test_workspace" + assert ds.datastores == datastores + +# Test the from_response class method with a valid response +def test_datastores_from_response(mocker): + mock_response = Mock() + mock_response.json.return_value = { + "dataStores": { + "workspace": {"name": "test_workspace"}, + "dataStore": [ + {"name": "store1"}, + {"name": "store2"} + ] + } + } + + ds = DataStores.from_response(mock_response) + + assert ds.workspace_name == "test_workspace" + assert ds.datastores == ["store1", "store2"] + + +# Test from_response with an empty response +def test_datastores_from_response_empty(): + mock_response = Mock() + mock_response.json.return_value = { + "dataStores": { + "workspace": {"name": "empty_workspace"}, + "dataStore": [] + } + } + + ds = DataStores.from_response(mock_response) + + assert ds.workspace_name == "empty_workspace" + assert ds.datastores == [] + +# Test the __repr__ method +def test_datastores_repr(): + ds = DataStores("test_workspace", ["store1", "store2"]) + + expected_repr = "['store1', 'store2']" + + assert repr(ds) == expected_repr