Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed upload_style so you can alternatively just pass valid XML in place of the path to the file containing the style XML #139

Merged
merged 9 commits into from
Feb 8, 2024
33 changes: 23 additions & 10 deletions geo/Geoserver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# inbuilt libraries
import os
from typing import List, Optional, Set
from typing import List, Optional, Set, Union
from pathlib import Path

# third-party libraries
import requests
Expand All @@ -9,7 +10,7 @@
# custom functions
from .Calculation_gdal import raster_value
from .Style import catagorize_xml, classified_xml, coverage_style_xml, outline_only_xml
from .supports import prepare_zip_file
from .supports import prepare_zip_file, is_valid_xml


# Custom exceptions.
Expand Down Expand Up @@ -1130,14 +1131,26 @@ def upload_style(
-----
The name of the style file will be, sld_name:workspace
This function will create the style file in a specified workspace.
Inputs: path to the sld_file, 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)
f = name.split(".")
if len(f) > 0:
name = f[0]

if Path(path).exists():
# path is pointing to an existing file
with open(path, "rb") as f:
xml = f.read()
elif is_valid_xml(path):
# path is actually just the xml itself
xml = path
else:
# path is non-existing file or not valid xml
raise ValueError("`path` must be either a path to a style file, or a valid XML string.")

headers = {"content-type": "text/xml"}

url = "{}/rest/workspaces/{}/styles".format(self.service_url, workspace)
Expand All @@ -1158,13 +1171,13 @@ def upload_style(

r = self._requests(method="post", url=url, data=style_xml, headers=headers)
if r.status_code == 201:
with open(path, "rb") as f:
r_sld = requests.put(
url + "/" + name,
data=f.read(),
auth=(self.username, self.password),
headers=header_sld,
)
r_sld = requests.put(
url + "/" + name,
data=xml,
auth=(self.username, self.password),
headers=header_sld,
)

if r_sld.status_code == 200:
return r_sld.status_code
else:
Expand Down
23 changes: 23 additions & 0 deletions geo/supports.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from tempfile import mkstemp
from typing import Dict
from zipfile import ZipFile
import xml.etree.ElementTree as ET


def prepare_zip_file(name: str, data: Dict) -> str:
Expand Down Expand Up @@ -37,3 +38,25 @@ def prepare_zip_file(name: str, data: Dict) -> str:
zip_file.close()
os.close(fd)
return path


def is_valid_xml(xml_string: str) -> bool:

"""
Returns True if string is valid XML, false otherwise

Parameters
----------
xml_string : string containing xml

Returns
-------
bool
"""

try:
# Attempt to parse the XML string
ET.fromstring(xml_string)
return True
except ET.ParseError:
return False
86 changes: 86 additions & 0 deletions tests/data/style.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>generic</Name>
<UserStyle>
<Title>Generic</Title>
<Abstract>Generic style</Abstract>
<FeatureTypeStyle>
<Rule>
<Name>raster</Name>
<Title>Opaque Raster</Title>
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:Function name="isCoverage"/>
<ogc:Literal>true</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<RasterSymbolizer>
<Opacity>1.0</Opacity>
</RasterSymbolizer>
</Rule>
<Rule>
<Name>Polygon</Name>
<Title>Grey Polygon</Title>
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:Function name="dimension">
<ogc:Function name="geometry"/>
</ogc:Function>
<ogc:Literal>2</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#AAAAAA</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">1</CssParameter>
</Stroke>
</PolygonSymbolizer>
</Rule>
<Rule>
<Name>Line</Name>
<Title>Blue Line</Title>
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:Function name="dimension">
<ogc:Function name="geometry"/>
</ogc:Function>
<ogc:Literal>1</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#0000FF</CssParameter>
<CssParameter name="stroke-opacity">1</CssParameter>
</Stroke>
</LineSymbolizer>
</Rule>
<Rule>
<Name>point</Name>
<Title>Red Square Point</Title>
<ElseFilter/>
<PointSymbolizer>
<Graphic>
<Mark>
<WellKnownName>square</WellKnownName>
<Fill>
<CssParameter name="fill">#FF0000</CssParameter>
</Fill>
</Mark>
<Size>6</Size>
</Graphic>
</PointSymbolizer>
</Rule>
<VendorOption name="ruleEvaluation">first</VendorOption>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
56 changes: 56 additions & 0 deletions tests/test_geoserver.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import pathlib

import pytest

from geo.Style import catagorize_xml, classified_xml
from geo.Geoserver import GeoserverException

from .common import geo

HERE = pathlib.Path(__file__).parent.resolve()

@pytest.mark.skip(reason="Only setup for local testing.")
class TestRequest:
Expand Down Expand Up @@ -122,6 +126,58 @@ def test_styles(self):
)


class TestUploadStyles:

def test_upload_style_from_file(self):

try:
geo.delete_style("test_upload_style")
except GeoserverException:
pass

geo.upload_style(f"{HERE}/data/style.sld", "test_upload_style")
style = geo.get_style("test_upload_style")
assert style["style"]["name"] == "test_upload_style"

def test_upload_style_from_malformed_file_fails(self):

try:
geo.delete_style("style_doesnt_exist")
except GeoserverException:
pass

with pytest.raises(ValueError):
geo.upload_style(f"{HERE}/data/style_doesnt_exist.sld", "style_doesnt_exist")
with pytest.raises(GeoserverException):
style = geo.get_style("style_doesnt_exist")
print()

def test_upload_style_from_xml(self):

try:
geo.delete_style("test_upload_style")
except GeoserverException:
pass

xml = open(f"{HERE}/data/style.sld").read()
geo.upload_style(xml, "test_upload_style")
style = geo.get_style("test_upload_style")
assert style["style"]["name"] == "test_upload_style"

def test_upload_style_from_malformed_xml_fails(self):

try:
geo.delete_style("style_malformed")
except GeoserverException:
pass

xml = open(f"{HERE}/data/style.sld").read()[1:]
with pytest.raises(ValueError):
geo.upload_style(xml, "style_malformed")
with pytest.raises(GeoserverException):
style = geo.get_style("style_malformed")


@pytest.mark.skip(reason="Only setup for local testing.")
class TestPostGres:
from geo.Postgres import Db
Expand Down