From 57d9035a05178f56850f350f478121e4f989e36f Mon Sep 17 00:00:00 2001 From: isaac Date: Mon, 24 Jun 2024 21:10:42 +0200 Subject: [PATCH 1/2] ai-based doc cleanup --- docs/source/geo.rst | 4 + geo/Geoserver.py | 1383 +++++++++++++++++++++++++++++------------- requirements_dev.txt | 1 + 3 files changed, 965 insertions(+), 423 deletions(-) diff --git a/docs/source/geo.rst b/docs/source/geo.rst index f18a897..f3d4aa6 100644 --- a/docs/source/geo.rst +++ b/docs/source/geo.rst @@ -1,6 +1,10 @@ Python API Reference ==================== +**Disclamer**: The documentation in the code was initially written by people, but was then passed through an AI large +language model (specifically ChatGPT-4o) to fill in gaps and correct minor mistakes. The results were also validated by +a person, but it is possible that an AI "hallucination" has occurred that was not caught and resulted in an incorrect +documentation. Please `report an issue `_ you find one. The ``Geoserver`` class ------------------------ diff --git a/geo/Geoserver.py b/geo/Geoserver.py index e9c1212..65321d0 100644 --- a/geo/Geoserver.py +++ b/geo/Geoserver.py @@ -14,11 +14,34 @@ def _parse_request_options(request_options: Dict[str, Any]): + """ + Parse request options. + + Parameters + ---------- + request_options : dict + The request options to parse. + + Returns + ------- + dict + The parsed request options. + """ return request_options if request_options is not None else {} # Custom exceptions. class GeoserverException(Exception): + """ + Custom exception for Geoserver errors. + + Parameters + ---------- + status : int + The status code of the error. + message : str + The error message. + """ def __init__(self, status, message): self.status = status self.message = message @@ -27,11 +50,32 @@ def __init__(self, status, message): # call back class for reading the data class DataProvider: + """ + Data provider for reading data. + + Parameters + ---------- + data : str + The data to be read. + """ def __init__(self, data): self.data = data self.finished = False def read_cb(self, size): + """ + Read callback. + + Parameters + ---------- + size : int + The size of the data to read. + + Returns + ------- + str + The read data. + """ assert len(self.data) <= size if not self.finished: self.finished = True @@ -43,15 +87,38 @@ def read_cb(self, size): # callback class for reading the files class FileReader: + """ + File reader for reading files. + + Parameters + ---------- + fp : file object + The file object to read from. + """ def __init__(self, fp): self.fp = fp def read_callback(self, size): + """ + Read callback. + + Parameters + ---------- + size : int + The size of the data to read. + + Returns + ------- + str + The read data. + """ return self.fp.read(size) class Geoserver: """ + Geoserver class to interact with GeoServer REST API. + Attributes ---------- service_url : str @@ -60,6 +127,8 @@ class Geoserver: Login name for session. password: str Password for session. + request_options : dict + Additional parameters to be sent with each request. """ def __init__( @@ -74,23 +143,26 @@ def __init__( self.password = password self.request_options = request_options if request_options is not None else {} - # private request method to reduce repetition of putting auth(username,password) in all requests call. DRY principle - def _requests(self, method: str, url: str, **kwargs) -> requests.Response: - """ - Convenience wrapper to the requests library which automatically handles the authentication, as well as additonal - options to be passed to each request. + Convenience wrapper to the requests library which automatically handles the authentication, as well as additional options to be passed to each request. - Attributes + Parameters ---------- method : str Which method to use (`get`, `post`, `put`, `delete`) url : str URL to which to make the request + kwargs : dict + Additional arguments to pass to the request. + + Returns + ------- + requests.Response + The response object. """ if method.lower() == "post": @@ -102,15 +174,14 @@ def _requests(self, elif method.lower() == "delete": return requests.delete(url, auth=(self.username, self.password), **kwargs, **self.request_options) - # _______________________________________________________________________________________________ - # - # GEOSERVER AND SERVER SPECIFIC METHODS - # _______________________________________________________________________________________________ - # - def get_manifest(self): """ - Returns the manifest of the geoserver. The manifest is a JSON of all the loaded JARs on the GeoServer server. + Returns the manifest of the GeoServer. The manifest is a JSON of all the loaded JARs on the GeoServer server. + + Returns + ------- + dict + The manifest of the GeoServer. """ url = "{}/rest/about/manifest.json".format(self.service_url) r = self._requests("get", url) @@ -121,7 +192,12 @@ def get_manifest(self): def get_version(self): """ - Returns the version of the geoserver as JSON. It contains only the details of the high level components: GeoServer, GeoTools, and GeoWebCache + Returns the version of the GeoServer as JSON. It contains only the details of the high level components: GeoServer, GeoTools, and GeoWebCache. + + Returns + ------- + dict + The version information of the GeoServer. """ url = "{}/rest/about/version.json".format(self.service_url) r = self._requests("get", url) @@ -132,7 +208,12 @@ def get_version(self): def get_status(self): """ - Returns the status of the geoserver. It shows the status details of all installed and configured modules. + Returns the status of the GeoServer. It shows the status details of all installed and configured modules. + + Returns + ------- + dict + The status of the GeoServer. """ url = "{}/rest/about/status.json".format(self.service_url) r = self._requests("get", url) @@ -143,7 +224,12 @@ def get_status(self): def get_system_status(self): """ - It returns the system status of the geoserver. It returns a list of system-level information. Major operating systems (Linux, Windows and MacOX) are supported out of the box. + Returns the system status of the GeoServer. It returns a list of system-level information. Major operating systems (Linux, Windows, and MacOS) are supported out of the box. + + Returns + ------- + dict + The system status of the GeoServer. """ url = "{}/rest/about/system-status.json".format(self.service_url) r = self._requests("get", url) @@ -156,9 +242,12 @@ def reload(self): """ Reloads the GeoServer catalog and configuration from disk. - This operation is used in cases where an external tool has modified the on-disk configuration. - This operation will also force GeoServer to drop any internal caches and reconnect to all data stores. - curl -X POST http://localhost:8080/geoserver/rest/reload -H "accept: application/json" -H "content-type: application/json" + This operation is used in cases where an external tool has modified the on-disk configuration. This operation will also force GeoServer to drop any internal caches and reconnect to all data stores. + + Returns + ------- + str + The status code of the reload operation. """ url = "{}/rest/reload".format(self.service_url) r = self._requests("post", url) @@ -169,11 +258,12 @@ def reload(self): def reset(self): """ - Resets all store, raster, and schema caches. This operation is used to force GeoServer to drop all caches and - store connections and reconnect to each of them the next time they are needed by a request. This is useful in - case the stores themselves cache some information about the data structures they manage that may have changed - in the meantime. - curl -X POST http://localhost:8080/geoserver/rest/reset -H "accept: application/json" -H "content-type: application/json" + Resets all store, raster, and schema caches. This operation is used to force GeoServer to drop all caches and store connections and reconnect to each of them the next time they are needed by a request. This is useful in case the stores themselves cache some information about the data structures they manage that may have changed in the meantime. + + Returns + ------- + str + The status code of the reset operation. """ url = "{}/rest/reset".format(self.service_url) r = self._requests("post", url) @@ -182,17 +272,15 @@ def reset(self): else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # WORKSPACES - # _______________________________________________________________________________________________ - # - def get_default_workspace(self): """ Returns the default workspace. - """ + Returns + ------- + dict + The default workspace. + """ url = "{}/rest/workspaces/default".format(self.service_url) r = self._requests("get", url) @@ -203,13 +291,18 @@ def get_default_workspace(self): def get_workspace(self, workspace): """ - get name workspace if exist - Example: curl -v -u admin:admin -XGET -H "Accept: text/xml" http://localhost:8080/geoserver/rest/workspaces/acme.xml - """ + Get the name of a workspace if it exists. - # FIXME: params["recurse"]="true" was hardcoded historically, but it should be configurable now. Could be breaking change though - #request_options = {"params": {"recurse": "true"}} + Parameters + ---------- + workspace : str + The name of the workspace. + Returns + ------- + dict + The workspace information. + """ url = "{}/rest/workspaces/{}.json".format(self.service_url, workspace) r = self._requests("get", url, params={"recurse": "true"}) @@ -221,6 +314,11 @@ def get_workspace(self, workspace): def get_workspaces(self): """ Returns all the workspaces. + + Returns + ------- + dict + All the workspaces. """ url = "{}/rest/workspaces".format(self.service_url) r = self._requests("get", url) @@ -233,6 +331,16 @@ def get_workspaces(self): def set_default_workspace(self, workspace: str): """ Set the default workspace. + + Parameters + ---------- + workspace : str + The name of the workspace to set as default. + + Returns + ------- + str + The status code of the operation. """ url = "{}/rest/workspaces/default".format(self.service_url) data = "{}".format(workspace) @@ -253,9 +361,17 @@ def set_default_workspace(self, workspace: str): def create_workspace(self, workspace: str): """ - Create a new workspace in geoserver. + Create a new workspace in GeoServer. The GeoServer workspace URL will be the same as the name of the workspace. + + Parameters + ---------- + workspace : str + The name of the workspace to create. - The geoserver workspace url will be same as the name of the workspace. + Returns + ------- + str + The status code and message of the operation. """ url = "{}/rest/workspaces".format(self.service_url) data = "{}".format(workspace) @@ -269,11 +385,17 @@ def create_workspace(self, workspace: str): def delete_workspace(self, workspace: str): """ + Delete a workspace. Parameters ---------- workspace : str + The name of the workspace to delete. + Returns + ------- + str + The status code and message of the operation. """ payload = {"recurse": "true"} url = "{}/rest/workspaces/{}".format(self.service_url, workspace) @@ -285,18 +407,21 @@ def delete_workspace(self, workspace: str): else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # DATASTORES - # _______________________________________________________________________________________________ - # - def get_datastore(self, store_name: str, workspace: Optional[str] = None): """ - Return the data store in a given workspace. + Return the data store in a given workspace. If workspace is not provided, it will take the default workspace. - If workspace is not provided, it will take the default workspace - curl -X GET http://localhost:8080/geoserver/rest/workspaces/demo/datastores -H "accept: application/xml" -H "content-type: application/json" + Parameters + ---------- + store_name : str + The name of the data store. + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The data store information. """ if workspace is None: workspace = "default" @@ -314,10 +439,17 @@ def get_datastore(self, store_name: str, workspace: Optional[str] = None): def get_datastores(self, workspace: Optional[str] = None): """ - List all data stores in a workspace. + List all data stores in a workspace. If workspace is not provided, it will list all the datastores inside the default workspace. + + Parameters + ---------- + workspace : str, optional + The name of the workspace. - If workspace is not provided, it will listout all the datastores inside default workspace - curl -X GET http://localhost:8080/geoserver/rest/workspaces/demo/datastores -H "accept: application/xml" -H "content-type: application/json" + Returns + ------- + dict + The list of data stores. """ if workspace is None: workspace = "default" @@ -331,17 +463,23 @@ def get_datastores(self, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # COVERAGE STORES - # _______________________________________________________________________________________________ - # - def get_coveragestore( self, coveragestore_name: str, workspace: Optional[str] = None ): """ Returns the store name if it exists. + + Parameters + ---------- + coveragestore_name : str + The name of the coverage store. + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The coverage store information. """ payload = {"recurse": "true"} if workspace is None: @@ -350,7 +488,6 @@ def get_coveragestore( self.service_url, workspace, coveragestore_name ) r = self._requests(method="get", url=url, params=payload) - # print("Status code: {}, Get coverage store".format(r.status_code)) if r.status_code == 200: return r.json() @@ -359,7 +496,17 @@ def get_coveragestore( def get_coveragestores(self, workspace: str = None): """ - Returns all the coveragestores inside a specific workspace. + Returns all the coverage stores inside a specific workspace. + + Parameters + ---------- + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The list of coverage stores. """ if workspace is None: workspace = "default" @@ -379,23 +526,27 @@ def create_coveragestore( file_type: str = "GeoTIFF", content_type: str = "image/tiff", ): - """Creates the coveragestore; Data will uploaded to the server. + """ + Creates the coverage store; Data will be uploaded to the server. Parameters ---------- path : str + The path to the file. workspace : str, optional + The name of the workspace. layer_name : str, optional - The name of coveragestore. If not provided, parsed from the file name. + The name of the coverage store. If not provided, parsed from the file name. file_type : str + The type of the file. content_type : str - overwrite : bool + The content type of the file. - Notes - ----- - the path to the file and file_type indicating it is a geotiff, arcgrid or other raster type + Returns + ------- + dict + The response from the server. """ - if path is None: raise Exception("You must provide the full path to the raster") @@ -435,23 +586,28 @@ def publish_time_dimension_to_coveragestore( content_type: str = "application/xml; charset=UTF-8", ): """ - Create time dimension in coverage store to publish time series in geoserver. + Create time dimension in coverage store to publish time series in GeoServer. Parameters ---------- store_name : str, optional + The name of the coverage store. workspace : str, optional + The name of the workspace. presentation : str, optional + The presentation style. units : str, optional + The units of the time dimension. default_value : str, optional + The default value of the time dimension. content_type : str + The content type of the request. - Notes - ----- - More about time support in geoserver WMS you can read here: - https://docs.geoserver.org/master/en/user/services/wms/time.html + Returns + ------- + dict + The response from the server. """ - url = "{0}/rest/workspaces/{1}/coveragestores/{2}/coverages/{2}".format( self.service_url, workspace, store_name ) @@ -484,15 +640,21 @@ def publish_time_dimension_to_coveragestore( else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # LAYERS - # _______________________________________________________________________________________________ - # - def get_layer(self, layer_name: str, workspace: Optional[str] = None): """ Returns the layer by layer name. + + Parameters + ---------- + layer_name : str + The name of the layer. + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The layer information. """ url = "{}/rest/layers/{}".format(self.service_url, layer_name) if workspace is not None: @@ -508,8 +670,17 @@ def get_layer(self, layer_name: str, workspace: Optional[str] = None): def get_layers(self, workspace: Optional[str] = None): """ - Get all the layers from geoserver - If workspace is None, it will listout all the layers from geoserver + Get all the layers from GeoServer. If workspace is None, it will list all the layers from GeoServer. + + Parameters + ---------- + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The list of layers. """ url = "{}/rest/layers".format(self.service_url) @@ -523,12 +694,19 @@ def get_layers(self, workspace: Optional[str] = None): def delete_layer(self, layer_name: str, workspace: Optional[str] = None): """ + Delete a layer. Parameters ---------- layer_name : str + The name of the layer to delete. workspace : str, optional + The name of the workspace. + Returns + ------- + str + The status code and message of the operation. """ payload = {"recurse": "true"} url = "{}/rest/workspaces/{}/layers/{}".format( @@ -543,19 +721,19 @@ def delete_layer(self, layer_name: str, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # LAYER GROUPS - # _______________________________________________________________________________________________ - # - def get_layergroups(self, workspace: Optional[str] = None): """ - Returns all the layer groups from geoserver. + Returns all the layer groups from GeoServer. If workspace is None, it will list all the layer groups from GeoServer. - Notes - ----- - If workspace is None, it will list all the layer groups from geoserver. + Parameters + ---------- + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The list of layer groups. """ url = "{}/rest/layergroups".format(self.service_url) @@ -572,6 +750,18 @@ def get_layergroups(self, workspace: Optional[str] = None): def get_layergroup(self, layer_name: str, workspace: Optional[str] = None): """ Returns the layer group by layer group name. + + Parameters + ---------- + layer_name : str + The name of the layer group. + workspace : str, optional + The name of the workspace. + + Returns + ------- + dict + The layer group information. """ url = "{}/rest/layergroups/{}".format(self.service_url, layer_name) if workspace is not None: @@ -602,20 +792,28 @@ def create_layergroup( Parameters ---------- name : str + The name of the layer group. mode : str + The mode of the layer group. title : str + The title of the layer group. abstract_text : str + The abstract text of the layer group. layers : list + The list of layers in the layer group. workspace : str, optional + The name of the workspace. formats : str, optional + The format of the layer group. metadata : list, optional + The metadata of the layer group. keywords : list, optional + The keywords of the layer group. - Notes - ----- - title is a human readable text for the layergroup - abstract_text is a long text, like a brief info about the layergroup - workspace is Optional(Global Layergroups don't need workspace).A layergroup can exist without a workspace. + Returns + ------- + str + The URL of the created layer group. """ assert isinstance(name, str), "Name must be of type String:''" assert isinstance(mode, str), "Mode must be of type String:''" @@ -624,7 +822,7 @@ def create_layergroup( assert isinstance(formats, str), "Format must be of type String:''" assert isinstance( metadata, list - ), "Metadata must be of type List of dict:[{'about':'geoserver rest data metadata','content_url':'lint to content url'}]" + ), "Metadata must be of type List of dict:[{'about':'geoserver rest data metadata','content_url':'link to content url'}]" assert isinstance( keywords, list ), "Keywords must be of type List:['keyword1','keyword2'...]" @@ -634,9 +832,9 @@ def create_layergroup( if workspace: assert isinstance(workspace, str), "Workspace must be of type String:''" - # check if the workspace is valid in Geoserver + # check if the workspace is valid in GeoServer if self.get_workspace(workspace) is None: - raise Exception("Workspace is not valid in Geoserver Instance") + raise Exception("Workspace is not valid in GeoServer Instance") supported_modes: Set = { "single", @@ -657,14 +855,14 @@ def create_layergroup( f"Format not supported. Acceptable formats are : {supported_formats}" ) - # check if it already exist in Geoserver + # check if it already exist in GeoServer try: existing_layergroup = self.get_layergroup(name, workspace=workspace) except GeoserverException: existing_layergroup = None if existing_layergroup is not None: - raise Exception(f"Layergroup: {name} already exist in Geoserver instance") + raise Exception(f"Layergroup: {name} already exist in GeoServer instance") if len(layers) == 0: raise Exception("No layer provided!") @@ -686,7 +884,7 @@ def create_layergroup( ) except GeoserverException: raise Exception( - f"Layer: {layer} is not a valid layer in the Geoserver instance" + f"Layer: {layer} is not a valid layer in the GeoServer instance" ) skeleton = "" @@ -746,6 +944,7 @@ def create_layergroup( data = f""" + {name} {mode} {title} @@ -766,27 +965,41 @@ def create_layergroup( raise GeoserverException(r.status_code, r.content) def update_layergroup( - self, - layergroup_name, - title: Optional[str] = None, - abstract_text: Optional[str] = None, - formats: str = "html", - metadata: List[dict] = [], - keywords: List[str] = [], + self, + layergroup_name, + title: Optional[str] = None, + abstract_text: Optional[str] = None, + formats: str = "html", + metadata: List[dict] = [], + keywords: List[str] = [], ) -> str: """ Updates a Layergroup. Parameters ---------- - layergroup_name: str, required - mode : str, optional + layergroup_name: str + The name of the layergroup to update. title : str, optional + The new title for the layergroup. abstract_text : str, optional + The new abstract text for the layergroup. formats : str, optional - metadata : list, optional - keywords : list, optional + The format of the response. Default is "html". + metadata : list of dict, optional + List of metadata entries where each entry is a dictionary with "about" and "content_url" keys. + keywords : list of str, optional + List of keywords associated with the layergroup. + Returns + ------- + str + A success message indicating that the layergroup was updated. + + Raises + ------ + GeoserverException + If there is an issue updating the layergroup. """ # check if layergroup is valid in Geoserver @@ -846,7 +1059,7 @@ def update_layergroup( keyword_xml_list: List[str] = [ f"{keyword}" for keyword in keywords ] - keywords_xml: str = f"{''.join(['{}'] * len(keywords)).format(*keyword_xml_list)}" + keywords_xml: str = f"{''.join(['{}'] * len(keyword_xml_list)).format(*keyword_xml_list)}" skeleton += keywords_xml data = f""" @@ -872,7 +1085,7 @@ def update_layergroup( raise GeoserverException(r.status_code, r.content) def delete_layergroup( - self, layergroup_name: str, workspace: Optional[str] = None + self, layergroup_name: str, workspace: Optional[str] = None ) -> str: """ Delete a layer group from the geoserver and raise an exception @@ -880,8 +1093,20 @@ def delete_layergroup( Parameters ---------- - layergroup_name: str, required The name of the layer group to be deleted - workspace: str, optional The workspace the layergroup is located in + layergroup_name: str + The name of the layer group to be deleted. + workspace: str, optional + The workspace the layergroup is located in. + + Returns + ------- + str + A success message indicating that the layer group was deleted. + + Raises + ------ + GeoserverException + If there is an issue deleting the layergroup. """ # raises an exception in case the layer group doesn't exist self.get_layergroup(layer_name=layergroup_name, workspace=workspace) @@ -898,11 +1123,11 @@ def delete_layergroup( raise GeoserverException(r.status_code, r.content) def add_layer_to_layergroup( - self, - layer_name: str, - layer_workspace: str, - layergroup_name: str, - layergroup_workspace: str = None, + self, + layer_name: str, + layer_workspace: str, + layergroup_name: str, + layergroup_workspace: str = None, ) -> None: """ Add the specified layer to an existing layer group and raise an exception if @@ -910,13 +1135,26 @@ def add_layer_to_layergroup( Parameters ---------- - layer_name: str, required The name of the layer - layer_workspace: str, required The workspace the layer is located in - layergroup_workspace: str, optional The workspace the layergroup is located in - layergroup_name: str, required The name of the layer group - layergroup_workspace: str, optional The workspace the layergroup is located in - """ + layer_name: str + The name of the layer. + layer_workspace: str + The workspace the layer is located in. + layergroup_workspace: str, optional + The workspace the layergroup is located in. + layergroup_name: str + The name of the layer group. + layergroup_workspace: str, optional + The workspace the layergroup is located in. + Returns + ------- + None + + Raises + ------ + GeoserverException + If there is an issue adding the layer to the layergroup. + """ layergroup_info = self.get_layergroup( layer_name=layergroup_name, workspace=layergroup_workspace ) @@ -962,11 +1200,11 @@ def add_layer_to_layergroup( raise GeoserverException(r.status_code, r.content) def remove_layer_from_layergroup( - self, - layer_name: str, - layer_workspace: str, - layergroup_name: str, - layergroup_workspace: str = None, + self, + layer_name: str, + layer_workspace: str, + layergroup_name: str, + layergroup_workspace: str = None, ) -> None: """ Add remove the specified layer from an existing layer group and raise an exception if @@ -974,13 +1212,26 @@ def remove_layer_from_layergroup( Parameters ---------- - layer_name: str, required The name of the layer - layer_workspace: str, required The workspace the layer is located in - layergroup_workspace: str, optional The workspace the layergroup is located in - layergroup_name: str, required The name of the layer group - layergroup_workspace: str, optional The workspace the layergroup is located in - """ + layer_name: str + The name of the layer. + layer_workspace: str + The workspace the layer is located in. + layergroup_workspace: str, optional + The workspace the layergroup is located in. + layergroup_name: str + The name of the layer group. + layergroup_workspace: str, optional + The workspace the layergroup is located in. + + Returns + ------- + None + Raises + ------ + GeoserverException + If there is an issue removing the layer from the layergroup. + """ layergroup_info = self.get_layergroup( layer_name=layergroup_name, workspace=layergroup_workspace ) @@ -1033,21 +1284,23 @@ def remove_layer_from_layergroup( raise GeoserverException(r.status_code, r.content) def _layergroup_definition_from_layers_and_styles( - self, publishables: list, styles: list + self, publishables: list, styles: list ) -> str: """ - Helper function for add_layer_to_layergroup and remove_layer_from_layergroup + Helper function for add_layer_to_layergroup and remove_layer_from_layergroup. Parameters ---------- - layer_name: str, required The name of the layer - layer_workspace: str, required The workspace the layer is located in + publishables: list + List of publishable layers. + styles: list + List of styles associated with the publishable layers. Returns ------- - Formatted xml request body for PUT layergroup + str + Formatted XML request body for PUT layergroup. """ - # the get_layergroup method may return an empty string for style; # so we get the default styles for each layer with no style information in the layergroup if len(styles) == 1: @@ -1101,15 +1354,26 @@ def _layergroup_definition_from_layers_and_styles( return data - # _______________________________________________________________________________________________ - # - # STYLES - # _______________________________________________________________________________________________ - # - def get_style(self, style_name, workspace: Optional[str] = None): """ Returns the style by style name. + + Parameters + ---------- + style_name: str + The name of the style. + workspace: str, optional + The workspace the style is located in. + + Returns + ------- + dict + A dictionary representation of the style. + + Raises + ------ + GeoserverException + If there is an issue retrieving the style. """ url = "{}/rest/styles/{}.json".format(self.service_url, style_name) if workspace is not None: @@ -1127,6 +1391,21 @@ def get_style(self, style_name, workspace: Optional[str] = None): def get_styles(self, workspace: Optional[str] = None): """ Returns all loaded styles from geoserver. + + Parameters + ---------- + workspace: str, optional + The workspace to filter the styles by. + + Returns + ------- + dict + A dictionary containing all the styles. + + Raises + ------ + GeoserverException + If there is an issue retrieving the styles. """ url = "{}/rest/styles.json".format(self.service_url) @@ -1141,26 +1420,35 @@ def get_styles(self, workspace: Optional[str] = None): raise GeoserverException(r.status_code, r.content) def upload_style( - self, - path: str, - name: Optional[str] = None, - workspace: Optional[str] = None, - sld_version: str = "1.0.0", + self, + path: str, + name: Optional[str] = None, + workspace: Optional[str] = None, + sld_version: str = "1.0.0", ): - """Uploads a style file to geoserver. + """ + Uploads a style file to geoserver. + Parameters ---------- path : str + Path to the style file or XML string. name : str, optional + The name of the style. If None, the name is parsed from the file name. workspace : str, optional + The workspace to upload the style to. sld_version : str, optional + The version of the SLD. Default is "1.0.0". - Notes - ----- - The name of the style file will be, sld_name:workspace - This function will create the style file in a specified workspace. - `path` can either be the path to the SLD file itself, or a string containing valid XML to be used for the style - Inputs: path to the sld_file or the contents of an SLD file itself, workspace, + Returns + ------- + int + The status code of the request. + + Raises + ------ + GeoserverException + If there is an issue uploading the style. """ if name is None: name = os.path.basename(path) @@ -1215,35 +1503,45 @@ def upload_style( raise GeoserverException(r.status_code, r.content) def create_coveragestyle( - self, - raster_path: str, - style_name: Optional[str] = None, - workspace: str = None, - color_ramp: str = "RdYlGn_r", - cmap_type: str = "ramp", - number_of_classes: int = 5, - opacity: float = 1, + self, + raster_path: str, + style_name: Optional[str] = None, + workspace: str = None, + color_ramp: str = "RdYlGn_r", + cmap_type: str = "ramp", + number_of_classes: int = 5, + opacity: float = 1, ): - """Dynamically create style for raster. + """ + Dynamically create style for raster. Parameters ---------- raster_path : str + Path to the raster file. style_name : str, optional + The name of the style. If None, the name is parsed from the raster file name. workspace : str + The workspace to create the style in. color_ramp : str - cmap_type : str TODO: This should be a set of the available options : {"ramp", "linear", ... } + The color ramp to use. + cmap_type : str + The type of color map. number_of_classes : int + The number of classes. opacity : float - overwrite : bool + The opacity of the style. - Notes - ----- - The name of the style file will be, rasterName:workspace - This function will dynamically create the style file for raster. - Inputs: name of file, workspace, cmap_type (two options: values, range), ncolors: determines the number of class, min for minimum value of the raster, max for the max value of raster - """ + Returns + ------- + int + The status code of the request. + Raises + ------ + GeoserverException + If there is an issue creating the style. + """ raster = raster_value(raster_path) min_value = raster["min"] max_value = raster["max"] @@ -1300,33 +1598,41 @@ def create_coveragestyle( raise GeoserverException(r.status_code, r.content) def create_catagorized_featurestyle( - self, - style_name: str, - column_name: str, - column_distinct_values, - workspace: str = None, - color_ramp: str = "tab20", - geom_type: str = "polygon", + self, + style_name: str, + column_name: str, + column_distinct_values, + workspace: str = None, + color_ramp: str = "tab20", + geom_type: str = "polygon", ): - """Dynamically create categorized style for postgis geometry, + """ + Dynamically create categorized style for postgis geometry, Parameters ---------- style_name : str + The name of the style. column_name : str + The column name to base the style on. column_distinct_values + The distinct values in the column. workspace : str + The workspace to create the style in. color_ramp : str + The color ramp to use. geom_type : str - outline_color : str - overwrite : bool + The geometry type (point, line, polygon). - Notes - ----- + Returns + ------- + int + The status code of the request. - The data type must be point, line or polygon - Inputs: column_name (based on which column style should be generated), workspace, - color_or_ramp (color should be provided in hex code or the color ramp name, geom_type(point, line, polygon), outline_color(hex_color)) + Raises + ------ + GeoserverException + If there is an issue creating the style. """ catagorize_xml(column_name, column_distinct_values, color_ramp, geom_type) @@ -1366,30 +1672,36 @@ def create_catagorized_featurestyle( raise GeoserverException(r.status_code, r.content) def create_outline_featurestyle( - self, - style_name: str, - color: str = "#3579b1", - width: str = "2", - geom_type: str = "polygon", - workspace: Optional[str] = None, + self, + style_name: str, + color: str = "#3579b1", + width: str = "2", + geom_type: str = "polygon", + workspace: Optional[str] = None, ): - """Dynamically creates the outline style for postgis geometry + """ + Dynamically creates the outline style for postgis geometry Parameters ---------- style_name : str + The name of the style. color : str + The color of the outline. geom_type : str + The geometry type (point, line, polygon). workspace : str, optional - overwrite : bool + The workspace to create the style in. Returns ------- + int + The status code of the request. - Notes - ----- - The geometry type must be point, line or polygon - Inputs: style_name (name of the style file in geoserver), workspace, color (style color) + Raises + ------ + GeoserverException + If there is an issue creating the style. """ outline_only_xml(color, width, geom_type) @@ -1429,31 +1741,42 @@ def create_outline_featurestyle( raise GeoserverException(r.status_code, r.content) def create_classified_featurestyle( - self, - style_name: str, - column_name: str, - column_distinct_values, - workspace: Optional[str] = None, - color_ramp: str = "tab20", - geom_type: str = "polygon", - # outline_color: str = "#3579b1", + self, + style_name: str, + column_name: str, + column_distinct_values, + workspace: Optional[str] = None, + color_ramp: str = "tab20", + geom_type: str = "polygon", + # outline_color: str = "#3579b1", ): - """Dynamically creates the classified style for postgis geometries. + """ + Dynamically creates the classified style for postgis geometries. Parameters ---------- style_name : str + The name of the style. column_name : str + The column name to base the style on. column_distinct_values + The distinct values in the column. workspace : str, optional + The workspace to create the style in. color_ramp : str - overwrite : bool + The color ramp to use. + geom_type : str + The geometry type (point, line, polygon). - Notes - ----- - The data type must be point, line or polygon - Inputs: column_name (based on which column style should be generated), workspace, - color_or_ramp (color should be provided in hex code or the color ramp name, geom_type(point, line, polygon), outline_color(hex_color)) + Returns + ------- + int + The status code of the request. + + Raises + ------ + GeoserverException + If there is an issue creating the style. """ classified_xml( style_name, @@ -1499,25 +1822,32 @@ def create_classified_featurestyle( raise GeoserverException(r.status_code, r.content) def publish_style( - self, - layer_name: str, - style_name: str, - workspace: str, + self, + layer_name: str, + style_name: str, + workspace: str, ): - """Publish a raster file to geoserver. + """ + Publish a raster file to geoserver. Parameters ---------- layer_name : str + The name of the layer. style_name : str + The name of the style. workspace : str + The workspace the layer is located in. - Notes - ----- - The coverage store will be created automatically as the same name as the raster layer name. - input parameters: the parameters connecting geoserver (user,password, url and workspace name), - the path to the file and file_type indicating it is a geotiff, arcgrid or other raster type. + Returns + ------- + int + The status code of the request. + Raises + ------ + GeoserverException + If there is an issue publishing the style. """ headers = {"content-type": "text/xml"} url = "{}/rest/layers/{}:{}".format(self.service_url, workspace, layer_name) @@ -1540,11 +1870,24 @@ def publish_style( def delete_style(self, style_name: str, workspace: Optional[str] = None): """ + Delete a style from the geoserver. Parameters ---------- style_name : str + The name of the style. workspace : str, optional + The workspace the style is located in. + + Returns + ------- + str + A success message indicating that the style was deleted. + + Raises + ------ + GeoserverException + If there is an issue deleting the style. """ payload = {"recurse": "true"} url = "{}/rest/workspaces/{}/styles/{}".format( @@ -1560,43 +1903,37 @@ def delete_style(self, style_name: str, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # FEATURES AND DATASTORES - # _______________________________________________________________________________________________ - # - def create_featurestore( - self, - store_name: str, - workspace: Optional[str] = None, - db: str = "postgres", - host: str = "localhost", - port: int = 5432, - schema: str = "public", - pg_user: str = "postgres", - pg_password: str = "admin", - overwrite: bool = False, - expose_primary_keys: str = "false", - description: Optional[str] = None, - evictor_run_periodicity: Optional[int] = 300, - max_open_prepared_statements: Optional[int] = 50, - encode_functions: Optional[str] = "false", - primary_key_metadata_table: Optional[str] = None, - batch_insert_size: Optional[int] = 1, - preparedstatements: Optional[str] = "false", - loose_bbox: Optional[str] = "true", - estimated_extends: Optional[str] = "true", - fetch_size: Optional[int] = 1000, - validate_connections: Optional[str] = "true", - support_on_the_fly_geometry_simplification: Optional[str] = "true", - connection_timeout: Optional[int] = 20, - create_database: Optional[str] = "false", - min_connections: Optional[int] = 1, - max_connections: Optional[int] = 10, - evictor_tests_per_run: Optional[int] = 3, - test_while_idle: Optional[str] = "true", - max_connection_idle_time: Optional[int] = 300, + self, + store_name: str, + workspace: Optional[str] = None, + db: str = "postgres", + host: str = "localhost", + port: int = 5432, + schema: str = "public", + pg_user: str = "postgres", + pg_password: str = "admin", + overwrite: bool = False, + expose_primary_keys: str = "false", + description: Optional[str] = None, + evictor_run_periodicity: Optional[int] = 300, + max_open_prepared_statements: Optional[int] = 50, + encode_functions: Optional[str] = "false", + primary_key_metadata_table: Optional[str] = None, + batch_insert_size: Optional[int] = 1, + preparedstatements: Optional[str] = "false", + loose_bbox: Optional[str] = "true", + estimated_extends: Optional[str] = "true", + fetch_size: Optional[int] = 1000, + validate_connections: Optional[str] = "true", + support_on_the_fly_geometry_simplification: Optional[str] = "true", + connection_timeout: Optional[int] = 20, + create_database: Optional[str] = "false", + min_connections: Optional[int] = 1, + max_connections: Optional[int] = 10, + evictor_tests_per_run: Optional[int] = 3, + test_while_idle: Optional[str] = "true", + max_connection_idle_time: Optional[int] = 300, ): """ Create PostGIS store for connecting postgres with geoserver. @@ -1604,40 +1941,73 @@ def create_featurestore( Parameters ---------- store_name : str + The name of the feature store. workspace : str, optional + The workspace to create the feature store in. db : str + The database type. Default is "postgres". host : str + The database host. Default is "localhost". port : int + The database port. Default is 5432. schema : str + The database schema. Default is "public". pg_user : str + The database user. Default is "postgres". pg_password : str + The database password. Default is "admin". overwrite : bool - - expose_primary_keys: str + Whether to overwrite the existing feature store. + expose_primary_keys : str + Whether to expose primary keys. Default is "false". description : str, optional - evictor_run_periodicity : str - max_open_prepared_statements : int - encode_functions : str - primary_key_metadata_table : str - batch_insert_size : int - preparedstatements : str - loose_bbox : str - estimated_extends : str - fetch_size : int - validate_connections : str - support_on_the_fly_geometry_simplification : str - connection_timeout : int - create_database : str - min_connections : int - max_connections : int - evictor_tests_per_run : int - test_while_idle : str - max_connection_idle_time : int - - - Notes - ----- - After creating feature store, you need to publish it. See the layer publish guidline here: https://geoserver-rest.readthedocs.io/en/latest/how_to_use.html#creating-and-publishing-featurestores-and-featurestore-layers + The description of the feature store. + evictor_run_periodicity : int, optional + The periodicity of the evictor run. + max_open_prepared_statements : int, optional + The maximum number of open prepared statements. + encode_functions : str, optional + Whether to encode functions. Default is "false". + primary_key_metadata_table : str, optional + The primary key metadata table. + batch_insert_size : int, optional + The batch insert size. Default is 1. + preparedstatements : str, optional + Whether to use prepared statements. Default is "false". + loose_bbox : str, optional + Whether to use loose bounding boxes. Default is "true". + estimated_extends : str, optional + Whether to use estimated extends. Default is "true". + fetch_size : int, optional + The fetch size. Default is 1000. + validate_connections : str, optional + Whether to validate connections. Default is "true". + support_on_the_fly_geometry_simplification : str, optional + Whether to support on-the-fly geometry simplification. Default is "true". + connection_timeout : int, optional + The connection timeout. Default is 20. + create_database : str, optional + Whether to create the database. Default is "false". + min_connections : int, optional + The minimum number of connections. Default is 1. + max_connections : int, optional + The maximum number of connections. Default is 10. + evictor_tests_per_run : int, optional + The number of evictor tests per run. + test_while_idle : str, optional + Whether to test while idle. Default is "true". + max_connection_idle_time : int, optional + The maximum connection idle time. Default is 300. + + Returns + ------- + str + A success message indicating that the feature store was created/updated. + + Raises + ------ + GeoserverException + If there is an issue creating/updating the feature store. """ url = "{}/rest/workspaces/{}/datastores".format(self.service_url, workspace) @@ -1731,11 +2101,11 @@ def create_featurestore( raise GeoserverException(r.status_code, r.content) def create_datastore( - self, - name: str, - path: str, - workspace: Optional[str] = None, - overwrite: bool = False, + self, + name: str, + path: str, + workspace: Optional[str] = None, + overwrite: bool = False, ): """ Create a datastore within the GeoServer. @@ -1743,19 +2113,25 @@ def create_datastore( Parameters ---------- name : str - Name of datastore to be created. - After creating the datastore, you need to publish it by using publish_featurestore function. + Name of datastore to be created. After creating the datastore, you need to publish it by using publish_featurestore function. path : str Path to shapefile (.shp) file, GeoPackage (.gpkg) file, WFS url (e.g. http://localhost:8080/geoserver/wfs?request=GetCapabilities) or directory containing shapefiles. - workspace : str, optional default value = "default". + workspace : str, optional + The workspace to create the datastore in. Default is "default". overwrite : bool + Whether to overwrite the existing datastore. - Notes - ----- - If you have PostGIS datastore, please use create_featurestore function - """ + Returns + ------- + str + A success message indicating that the datastore was created/updated. + Raises + ------ + GeoserverException + If there is an issue creating/updating the datastore. + """ if workspace is None: workspace = "default" @@ -1790,11 +2166,11 @@ def create_datastore( raise GeoserverException(r.status_code, r.content) def create_shp_datastore( - self, - path: str, - store_name: Optional[str] = None, - workspace: Optional[str] = None, - file_extension: str = "shp", + self, + path: str, + store_name: Optional[str] = None, + workspace: Optional[str] = None, + file_extension: str = "shp", ): """ Create datastore for a shapefile. @@ -1808,12 +2184,18 @@ def create_shp_datastore( workspace: str, optional Name of workspace to be used. Default: "default". file_extension : str + The file extension of the shapefile. Default is "shp". - Notes - ----- - The layer name will be assigned according to the shp name - """ + Returns + ------- + str + A success message indicating that the shapefile datastore was created. + Raises + ------ + GeoserverException + If there is an issue creating the shapefile datastore. + """ if path is None: raise Exception("You must provide a full path to shapefile") @@ -1851,11 +2233,11 @@ def create_shp_datastore( raise GeoserverException(r.status_code, r.content) def create_gpkg_datastore( - self, - path: str, - store_name: Optional[str] = None, - workspace: Optional[str] = None, - file_extension: str = "gpkg", + self, + path: str, + store_name: Optional[str] = None, + workspace: Optional[str] = None, + file_extension: str = "gpkg", ): """ Create datastore for a geopackage. @@ -1869,13 +2251,18 @@ def create_gpkg_datastore( workspace: str, optional Name of workspace to be used. Default: "default". file_extension : str + The file extension of the geopackage. Default is "gpkg". - Notes - ----- - The layer name will be assigned according to the layer name in the geopackage. - If the layer already exist it will be updated. - """ + Returns + ------- + str + A success message indicating that the geopackage datastore was created. + Raises + ------ + GeoserverException + If there is an issue creating the geopackage datastore. + """ if path is None: raise Exception("You must provide a full path to shapefile") @@ -1910,37 +2297,47 @@ def create_gpkg_datastore( raise GeoserverException(r.status_code, r.content) def publish_featurestore( - self, - store_name: str, - pg_table: str, - workspace: Optional[str] = None, - title: Optional[str] = None, - advertised: Optional[bool] = True, - abstract: Optional[str] = None, - keywords: Optional[List[str]] = None, - cqlfilter: Optional[str] = None - ): + self, + store_name: str, + pg_table: str, + workspace: Optional[str] = None, + title: Optional[str] = None, + advertised: Optional[bool] = True, + abstract: Optional[str] = None, + keywords: Optional[List[str]] = None, + cqlfilter: Optional[str] = None + ) -> int: """ Publish a featurestore to geoserver. Parameters ---------- store_name : str + The name of the featurestore. pg_table : str + The name of the PostgreSQL table. workspace : str, optional + The workspace to publish the featurestore in. Default is "default". title : str, optional + The title of the featurestore. If None, the table name is used. advertised : bool, optional + Whether to advertise the featurestore. Default is True. abstract : str, optional - keywords : list, optional + The abstract of the featurestore. + keywords : list of str, optional + List of keywords associated with the featurestore. cqlfilter : str, optional + The CQL filter for the featurestore. Returns ------- + int + The status code of the request. - Notes - ----- - Only user for postgis vector data - input parameters: specify the name of the table in the postgis database to be published, specify the store,workspace name, and the Geoserver user name, password and URL + Raises + ------ + GeoserverException + If there is an issue publishing the featurestore. """ if workspace is None: workspace = "default" @@ -1982,34 +2379,47 @@ def publish_featurestore( raise GeoserverException(r.status_code, r.content) def edit_featuretype( - self, - store_name: str, - workspace: Optional[str], - pg_table: str, - name: str, - title: str, - abstract: Optional[str] = None, - keywords: Optional[List[str]] = None, - recalculate : Optional[str] = None - ): - """ + self, + store_name: str, + workspace: Optional[str], + pg_table: str, + name: str, + title: str, + abstract: Optional[str] = None, + keywords: Optional[List[str]] = None, + recalculate: Optional[str] = None + ) -> int: + """ + Edit a featuretype in the geoserver. Parameters ---------- - recalculate : str, optional. Recalculate param. Can be: empty string, nativebbox and nativebbox,latlonbbox + recalculate : str, optional + Recalculate param. Can be: empty string, nativebbox and nativebbox,latlonbbox. store_name : str + The name of the feature store. workspace : str, optional + The workspace of the feature store. pg_table : str + The name of the PostgreSQL table. name : str + The name of the feature type. title : str + The title of the feature type. abstract : str, optional - keywords : list, optional + The abstract of the feature type. + keywords : list of str, optional + List of keywords associated with the feature type. Returns ------- + int + The status code of the request. - Notes - ----- + Raises + ------ + GeoserverException + If there is an issue editing the feature type. """ if workspace is None: workspace = "default" @@ -2029,7 +2439,6 @@ def edit_featuretype( keywords_xml += f"{keyword}" keywords_xml += "" - layer_xml = f""" {name} {title} @@ -2049,52 +2458,51 @@ def edit_featuretype( raise GeoserverException(r.status_code, r.content) def publish_featurestore_sqlview( - self, - name: str, - store_name: str, - sql: str, - parameters: Optional[Iterable[Dict]] = None, - key_column: Optional[str] = None, - geom_name: str = "geom", - geom_type: str = "Geometry", - srid: Optional[int] = 4326, - workspace: Optional[str] = None, - ): - """ - Publishes an SQL query as a layer, optionally with parameters + self, + name: str, + store_name: str, + sql: str, + parameters: Optional[Iterable[Dict]] = None, + key_column: Optional[str] = None, + geom_name: str = "geom", + geom_type: str = "Geometry", + srid: Optional[int] = 4326, + workspace: Optional[str] = None, + ) -> int: + """ + Publishes an SQL query as a layer, optionally with parameters. Parameters ---------- name : str + The name of the SQL view. store_name : str + The name of the feature store. sql : str - parameters : iterable of dicts, optional + The SQL query. + parameters : iterable of dict, optional + List of parameters for the SQL query. key_column : str, optional + The key column. geom_name : str, optional + The name of the geometry column. geom_type : str, optional + The type of the geometry column. + srid : int, optional + The spatial reference ID. Default is 4326. workspace : str, optional + The workspace to publish the SQL view in. Default is "default". - Notes - ----- - With regards to SQL view parameters, it is advised to read the relevant section from the geoserver docs: - https://docs.geoserver.org/main/en/user/data/database/sqlview.html#parameterizing-sql-views - - An integer-based parameter must have a default value - - You should be VERY careful with the `regexp_validator`, as it can open you to SQL injection attacks. If you do - not supply one for a parameter, it will use the geoserver default `^[\w\d\s]+$`. - - The `parameters` iterable must contain dictionaries with this structure: + Returns + ------- + int + The status code of the request. - ``` - { - "name": "" - "rexegpValidator": " (optional)" - "defaultValue" : "" - } - ``` + Raises + ------ + GeoserverException + If there is an issue publishing the SQL view. """ - if workspace is None: workspace = "default" @@ -2174,14 +2582,26 @@ def publish_featurestore_sqlview( else: raise GeoserverException(r.status_code, r.content) - def get_featuretypes(self, workspace: str = None, store_name: str = None): + def get_featuretypes(self, workspace: str = None, store_name: str = None) -> List[str]: """ + Get feature types from the geoserver. Parameters ---------- workspace : str + The workspace to get the feature types from. store_name : str + The name of the feature store. + Returns + ------- + list of str + A list of feature types. + + Raises + ------ + GeoserverException + If there is an issue getting the feature types. """ url = "{}/rest/workspaces/{}/datastores/{}/featuretypes.json".format( self.service_url, workspace, store_name @@ -2195,16 +2615,29 @@ def get_featuretypes(self, workspace: str = None, store_name: str = None): raise GeoserverException(r.status_code, r.content) def get_feature_attribute( - self, feature_type_name: str, workspace: str, store_name: str - ): + self, feature_type_name: str, workspace: str, store_name: str + ) -> List[str]: """ + Get feature attributes from the geoserver. Parameters ---------- feature_type_name : str + The name of the feature type. workspace : str + The workspace of the feature store. store_name : str + The name of the feature store. + + Returns + ------- + list of str + A list of feature attributes. + Raises + ------ + GeoserverException + If there is an issue getting the feature attributes. """ url = "{}/rest/workspaces/{}/datastores/{}/featuretypes/{}.json".format( self.service_url, workspace, store_name, feature_type_name @@ -2219,14 +2652,26 @@ def get_feature_attribute( else: raise GeoserverException(r.status_code, r.content) - def get_featurestore(self, store_name: str, workspace: str): + def get_featurestore(self, store_name: str, workspace: str) -> dict: """ + Get a featurestore from the geoserver. Parameters ---------- store_name : str + The name of the feature store. workspace : str + The workspace of the feature store. + + Returns + ------- + dict + A dictionary representation of the feature store. + Raises + ------ + GeoserverException + If there is an issue getting the feature store. """ url = "{}/rest/workspaces/{}/datastores/{}".format( self.service_url, workspace, store_name @@ -2239,15 +2684,27 @@ def get_featurestore(self, store_name: str, workspace: str): raise GeoserverException(r.status_code, r.content) def delete_featurestore( - self, featurestore_name: str, workspace: Optional[str] = None - ): + self, featurestore_name: str, workspace: Optional[str] = None + ) -> str: """ + Delete a featurestore from the geoserver. Parameters ---------- featurestore_name : str + The name of the featurestore. workspace : str, optional + The workspace of the featurestore. + Returns + ------- + str + A success message indicating that the featurestore was deleted. + + Raises + ------ + GeoserverException + If there is an issue deleting the featurestore. """ payload = {"recurse": "true"} url = "{}/rest/workspaces/{}/datastores/{}".format( @@ -2263,15 +2720,27 @@ def delete_featurestore( raise GeoserverException(r.status_code, r.content) def delete_coveragestore( - self, coveragestore_name: str, workspace: Optional[str] = None - ): + self, coveragestore_name: str, workspace: Optional[str] = None + ) -> str: """ + Delete a coveragestore from the geoserver. Parameters ---------- coveragestore_name : str + The name of the coveragestore. workspace : str, optional + The workspace of the coveragestore. + + Returns + ------- + str + A success message indicating that the coveragestore was deleted. + Raises + ------ + GeoserverException + If there is an issue deleting the coveragestore. """ payload = {"recurse": "true"} url = "{}/rest/workspaces/{}/coveragestores/{}".format( @@ -2290,20 +2759,24 @@ def delete_coveragestore( else: raise GeoserverException(r.status_code, r.content) - # _______________________________________________________________________________________________ - # - # USERS AND USERGROUPS - # _______________________________________________________________________________________________ - # - - def get_all_users(self, service=None): + def get_all_users(self, service=None) -> dict: """ + Query all users in the provided user/group service, else default user/group service is queried. Parameters ---------- service: str, optional + The user/group service to query. - Query all users in the provided user/group service, else default user/group service is queried + Returns + ------- + dict + A dictionary containing all users. + + Raises + ------ + GeoserverException + If there is an issue getting the users. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2320,19 +2793,31 @@ def get_all_users(self, service=None): raise GeoserverException(r.status_code, r.content) def create_user( - self, username: str, password: str, enabled: bool = True, service=None - ): + self, username: str, password: str, enabled: bool = True, service=None + ) -> str: """ + Add a new user to the provided user/group service. Parameters ---------- username : str + The username of the new user. password: str + The password of the new user. enabled: bool + Whether the new user is enabled. service : str, optional + The user/group service to add the user to. - Add a new user to the provided user/group service - If no user/group service is provided, then the users is added to default user service + Returns + ------- + str + A success message indicating that the user was created. + + Raises + ------ + GeoserverException + If there is an issue creating the user. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2354,20 +2839,33 @@ def create_user( raise GeoserverException(r.status_code, r.content) def modify_user( - self, username: str, new_name=None, new_password=None, enable=None, service=None - ): + self, username: str, new_name=None, new_password=None, enable=None, service=None + ) -> str: """ + Modifies a user in the provided user/group service. Parameters ---------- username : str + The username of the user to modify. new_name : str, optional + The new username. new_password : str, optional + The new password. enable : bool, optional + Whether the user is enabled. service : str, optional + The user/group service to modify the user in. - Modifies a user in the provided user/group service - If no user/group service is provided, then the user in the default user service is modified + Returns + ------- + str + A success message indicating that the user was modified. + + Raises + ------ + GeoserverException + If there is an issue modifying the user. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2395,16 +2893,26 @@ def modify_user( else: raise GeoserverException(r.status_code, r.content) - def delete_user(self, username: str, service=None): + def delete_user(self, username: str, service=None) -> str: """ + Deletes user from the provided user/group service. Parameters ---------- username : str - user_service : str, optional + The username of the user to delete. + service : str, optional + The user/group service to delete the user from. - Deletes user from the provided user/group service - If no user/group service is provided, then the users is deleted from default user service + Returns + ------- + str + A success message indicating that the user was deleted. + + Raises + ------ + GeoserverException + If there is an issue deleting the user. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2420,15 +2928,24 @@ def delete_user(self, username: str, service=None): else: raise GeoserverException(r.status_code, r.content) - def get_all_usergroups(self, service=None): + def get_all_usergroups(self, service=None) -> dict: """ + Queries all the groups in the given user/group service. Parameters ---------- service : str, optional + The user/group service to query. - Queries all the groups in the given user/group service - If no user/group service is provided, default user/group service is used + Returns + ------- + dict + A dictionary containing all user groups. + + Raises + ------ + GeoserverException + If there is an issue getting the user groups. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2443,16 +2960,26 @@ def get_all_usergroups(self, service=None): else: raise GeoserverException(r.status_code, r.content) - def create_usergroup(self, group: str, service=None): + def create_usergroup(self, group: str, service=None) -> str: """ + Add a new usergroup to the provided user/group service. Parameters ---------- group : str + The name of the user group. service : str, optional + The user/group service to add the user group to. + + Returns + ------- + str + A success message indicating that the user group was created. - Add a new usergroup to the provided user/group service - If no user/group service is provided, then the usergroup is added to default user service + Raises + ------ + GeoserverException + If there is an issue creating the user group. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: @@ -2466,16 +2993,26 @@ def create_usergroup(self, group: str, service=None): else: raise GeoserverException(r.status_code, r.content) - def delete_usergroup(self, group: str, service=None): + def delete_usergroup(self, group: str, service=None) -> str: """ + Deletes given usergroup from provided user/group service. Parameters ---------- group : str + The name of the user group to delete. service : str, optional + The user/group service to delete the user group from. + + Returns + ------- + str + A success message indicating that the user group was deleted. - Deletes given usergroup from provided user/group service - If no user/group service is provided, then the usergroup deleted from default user service + Raises + ------ + GeoserverException + If there is an issue deleting the user group. """ url = "{}/rest/security/usergroup/".format(self.service_url) if service is None: diff --git a/requirements_dev.txt b/requirements_dev.txt index a92c8f0..e3c92a8 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,6 +4,7 @@ pytest black flake8 sphinx>=1.7 +sphinx-rtd-theme>=2.0 pre-commit environs ddt From 8fd01b302fac798c6e0ee04b56ab9d056abf1666 Mon Sep 17 00:00:00 2001 From: isaac Date: Tue, 2 Jul 2024 21:12:49 +0200 Subject: [PATCH 2/2] restored notes and other comments --- geo/Geoserver.py | 152 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/geo/Geoserver.py b/geo/Geoserver.py index 65321d0..b4a6232 100644 --- a/geo/Geoserver.py +++ b/geo/Geoserver.py @@ -174,6 +174,12 @@ def _requests(self, elif method.lower() == "delete": return requests.delete(url, auth=(self.username, self.password), **kwargs, **self.request_options) + # _______________________________________________________________________________________________ + # + # GEOSERVER AND SERVER SPECIFIC METHODS + # _______________________________________________________________________________________________ + # + def get_manifest(self): """ Returns the manifest of the GeoServer. The manifest is a JSON of all the loaded JARs on the GeoServer server. @@ -272,6 +278,12 @@ def reset(self): else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # WORKSPACES + # _______________________________________________________________________________________________ + # + def get_default_workspace(self): """ Returns the default workspace. @@ -407,6 +419,12 @@ def delete_workspace(self, workspace: str): else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # DATASTORES + # _______________________________________________________________________________________________ + # + def get_datastore(self, store_name: str, workspace: Optional[str] = None): """ Return the data store in a given workspace. If workspace is not provided, it will take the default workspace. @@ -463,6 +481,12 @@ def get_datastores(self, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # COVERAGE STORES + # _______________________________________________________________________________________________ + # + def get_coveragestore( self, coveragestore_name: str, workspace: Optional[str] = None ): @@ -546,6 +570,10 @@ def create_coveragestore( ------- dict The response from the server. + + Notes + ----- + the path to the file and file_type indicating it is a geotiff, arcgrid or other raster type """ if path is None: raise Exception("You must provide the full path to the raster") @@ -607,6 +635,11 @@ def publish_time_dimension_to_coveragestore( ------- dict The response from the server. + + Notes + ----- + More about time support in geoserver WMS you can read here: + https://docs.geoserver.org/master/en/user/services/wms/time.html """ url = "{0}/rest/workspaces/{1}/coveragestores/{2}/coverages/{2}".format( self.service_url, workspace, store_name @@ -640,6 +673,12 @@ def publish_time_dimension_to_coveragestore( else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # LAYERS + # _______________________________________________________________________________________________ + # + def get_layer(self, layer_name: str, workspace: Optional[str] = None): """ Returns the layer by layer name. @@ -721,6 +760,12 @@ def delete_layer(self, layer_name: str, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # LAYER GROUPS + # _______________________________________________________________________________________________ + # + def get_layergroups(self, workspace: Optional[str] = None): """ Returns all the layer groups from GeoServer. If workspace is None, it will list all the layer groups from GeoServer. @@ -734,6 +779,10 @@ def get_layergroups(self, workspace: Optional[str] = None): ------- dict The list of layer groups. + + Notes + ----- + If workspace is None, it will list all the layer groups from geoserver. """ url = "{}/rest/layergroups".format(self.service_url) @@ -814,6 +863,12 @@ def create_layergroup( ------- str The URL of the created layer group. + + Notes + ----- + title is a human readable text for the layergroup + abstract_text is a long text, like a brief info about the layergroup + workspace is Optional(Global Layergroups don't need workspace).A layergroup can exist without a workspace. """ assert isinstance(name, str), "Name must be of type String:''" assert isinstance(mode, str), "Mode must be of type String:''" @@ -1354,6 +1409,12 @@ def _layergroup_definition_from_layers_and_styles( return data + # _______________________________________________________________________________________________ + # + # STYLES + # _______________________________________________________________________________________________ + # + def get_style(self, style_name, workspace: Optional[str] = None): """ Returns the style by style name. @@ -1449,6 +1510,13 @@ def upload_style( ------ GeoserverException If there is an issue uploading the style. + + Notes + ----- + The name of the style file will be, sld_name:workspace + This function will create the style file in a specified workspace. + `path` can either be the path to the SLD file itself, or a string containing valid XML to be used for the style + Inputs: path to the sld_file or the contents of an SLD file itself, workspace, """ if name is None: name = os.path.basename(path) @@ -1541,6 +1609,12 @@ def create_coveragestyle( ------ GeoserverException If there is an issue creating the style. + + Notes + ----- + The name of the style file will be, rasterName:workspace + This function will dynamically create the style file for raster. + Inputs: name of file, workspace, cmap_type (two options: values, range), ncolors: determines the number of class, min for minimum value of the raster, max for the max value of raster """ raster = raster_value(raster_path) min_value = raster["min"] @@ -1633,6 +1707,13 @@ def create_catagorized_featurestyle( ------ GeoserverException If there is an issue creating the style. + + Notes + ----- + + The data type must be point, line or polygon + Inputs: column_name (based on which column style should be generated), workspace, + color_or_ramp (color should be provided in hex code or the color ramp name, geom_type(point, line, polygon), outline_color(hex_color)) """ catagorize_xml(column_name, column_distinct_values, color_ramp, geom_type) @@ -1702,6 +1783,11 @@ def create_outline_featurestyle( ------ GeoserverException If there is an issue creating the style. + + Notes + ----- + The geometry type must be point, line or polygon + Inputs: style_name (name of the style file in geoserver), workspace, color (style color) """ outline_only_xml(color, width, geom_type) @@ -1777,6 +1863,12 @@ def create_classified_featurestyle( ------ GeoserverException If there is an issue creating the style. + + Notes + ----- + The data type must be point, line or polygon + Inputs: column_name (based on which column style should be generated), workspace, + color_or_ramp (color should be provided in hex code or the color ramp name, geom_type(point, line, polygon), outline_color(hex_color)) """ classified_xml( style_name, @@ -1848,6 +1940,12 @@ def publish_style( ------ GeoserverException If there is an issue publishing the style. + + Notes + ----- + The coverage store will be created automatically as the same name as the raster layer name. + input parameters: the parameters connecting geoserver (user,password, url and workspace name), + the path to the file and file_type indicating it is a geotiff, arcgrid or other raster type. """ headers = {"content-type": "text/xml"} url = "{}/rest/layers/{}:{}".format(self.service_url, workspace, layer_name) @@ -1903,6 +2001,12 @@ def delete_style(self, style_name: str, workspace: Optional[str] = None): else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # FEATURES AND DATASTORES + # _______________________________________________________________________________________________ + # + def create_featurestore( self, store_name: str, @@ -2008,6 +2112,10 @@ def create_featurestore( ------ GeoserverException If there is an issue creating/updating the feature store. + + Notes + ----- + After creating feature store, you need to publish it. See the layer publish guidline here: https://geoserver-rest.readthedocs.io/en/latest/how_to_use.html#creating-and-publishing-featurestores-and-featurestore-layers """ url = "{}/rest/workspaces/{}/datastores".format(self.service_url, workspace) @@ -2131,6 +2239,10 @@ def create_datastore( ------ GeoserverException If there is an issue creating/updating the datastore. + + Notes + ----- + If you have PostGIS datastore, please use create_featurestore function """ if workspace is None: workspace = "default" @@ -2195,6 +2307,10 @@ def create_shp_datastore( ------ GeoserverException If there is an issue creating the shapefile datastore. + + Notes + ----- + The layer name will be assigned according to the shp name """ if path is None: raise Exception("You must provide a full path to shapefile") @@ -2262,6 +2378,11 @@ def create_gpkg_datastore( ------ GeoserverException If there is an issue creating the geopackage datastore. + + Notes + ----- + The layer name will be assigned according to the layer name in the geopackage. + If the layer already exist it will be updated. """ if path is None: raise Exception("You must provide a full path to shapefile") @@ -2338,6 +2459,11 @@ def publish_featurestore( ------ GeoserverException If there is an issue publishing the featurestore. + + Notes + ----- + Only user for postgis vector data + input parameters: specify the name of the table in the postgis database to be published, specify the store,workspace name, and the Geoserver user name, password and URL """ if workspace is None: workspace = "default" @@ -2502,6 +2628,26 @@ def publish_featurestore_sqlview( ------ GeoserverException If there is an issue publishing the SQL view. + + Notes + ----- + With regards to SQL view parameters, it is advised to read the relevant section from the geoserver docs: + https://docs.geoserver.org/main/en/user/data/database/sqlview.html#parameterizing-sql-views + + An integer-based parameter must have a default value + + You should be VERY careful with the `regexp_validator`, as it can open you to SQL injection attacks. If you do + not supply one for a parameter, it will use the geoserver default `^[\w\d\s]+$`. + + The `parameters` iterable must contain dictionaries with this structure: + + ``` + { + "name": "" + "rexegpValidator": " (optional)" + "defaultValue" : "" + } + ``` """ if workspace is None: workspace = "default" @@ -2759,6 +2905,12 @@ def delete_coveragestore( else: raise GeoserverException(r.status_code, r.content) + # _______________________________________________________________________________________________ + # + # USERS AND USERGROUPS + # _______________________________________________________________________________________________ + # + def get_all_users(self, service=None) -> dict: """ Query all users in the provided user/group service, else default user/group service is queried.