[Git][debian-gis-team/owslib][master] 4 commits: New upstream version 0.25.0

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Tue Aug 24 04:59:38 BST 2021



Bas Couwenberg pushed to branch master at Debian GIS Project / owslib


Commits:
34320685 by Bas Couwenberg at 2021-08-24T05:54:25+02:00
New upstream version 0.25.0
- - - - -
f054a534 by Bas Couwenberg at 2021-08-24T05:54:31+02:00
Update upstream source from tag 'upstream/0.25.0'

Update to upstream version '0.25.0'
with Debian dir cc6f577b1dbf8c61e207c2f34ba8a6673044b154
- - - - -
96e8a53d by Bas Couwenberg at 2021-08-24T05:54:46+02:00
New upstream release.

- - - - -
87a26470 by Bas Couwenberg at 2021-08-24T05:55:55+02:00
Set distribution to unstable.

- - - - -


20 changed files:

- CHANGES.rst
- + SECURITY.md
- VERSION.txt
- debian/changelog
- docs/en/usage.rst
- owslib/__init__.py
- owslib/feature/postrequest.py
- owslib/fes2.py
- + owslib/gml.py
- owslib/iso.py
- owslib/map/wms111.py
- owslib/map/wms130.py
- owslib/ogcapi/__init__.py
- owslib/ogcapi/features.py
- requirements.txt
- + tests/test_fes2.py
- tests/test_ogcapi_features_pygeoapi.py
- tests/test_ogcapi_records_pygeoapi.py
- tests/test_wfs_generic.py
- tests/test_wms_getmap.py


Changes:

=====================================
CHANGES.rst
=====================================
@@ -1,6 +1,20 @@
 Changes
 =======
 
+0.25.0 (2021-08-23)
+-------------------
+
+A full list of commits for 0.25.0 can be found at:
+
+https://github.com/geopython/OWSLib/commits/0.25.0
+
+- WMS: include custom headers (#763)
+- ISO: add MD_BrowseGraphic support (#757)
+- OGC API: fix bbox handling #779), request/response handling
+- WFS: add support for topological filters (#780)
+- various bug fixes
+
+
 0.24.1 (2021-05-10)
 -------------------
 


=====================================
SECURITY.md
=====================================
@@ -0,0 +1,15 @@
+# OWSLib Security Policy
+
+## Supported Versions
+
+Security/vulnerability reports **should not** be submitted through GitHub issues or public discussions, but instead please send your report 
+to **geopython-security nospam @ lists.osgeo.org** - (remove the blanks and 'nospam').  
+
+## Supported Versions
+
+The OWSLib developer team will release patches for security vulnerabilities for the following versions:
+
+| Version | Supported          |
+| ------- | ------------------ |
+| latest stable version | :white_check_mark: |
+| previous versions | :x:                |


=====================================
VERSION.txt
=====================================
@@ -1 +1 @@
-0.24.1
+0.25.0


=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+owslib (0.25.0-1) unstable; urgency=medium
+
+  * Team upload.
+  * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org>  Tue, 24 Aug 2021 05:55:44 +0200
+
 owslib (0.24.1-1) unstable; urgency=medium
 
   * Team upload.


=====================================
docs/en/usage.rst
=====================================
@@ -254,7 +254,7 @@ OGC API - Records 1.0
   >>> my_catalogue['description']
   'My catalogue'
   >>> my_catalogue_queryables = w.collection_queryables('my-catalogue')
-  >>> len(my_catalogue_queryables['queryables'])
+  >>> len(my_catalogue_queryables)
   8
   >>> my_catalogue_query = w.collection_items('my-catalogue')
   >>> my_catalogue_query['features'][0]['properties'].keys()


=====================================
owslib/__init__.py
=====================================
@@ -1 +1 @@
-__version__ = '0.24.1'
+__version__ = '0.25.0'


=====================================
owslib/feature/postrequest.py
=====================================
@@ -173,8 +173,12 @@ class PostRequest_2_0_0(PostRequest):
 
         Cannot be used with set_bbox() or set_featureid().
         """
-        f = etree.fromstring(filter)
-        sub_elem = f.find(util.nspath("Filter", FES_NAMESPACE))
+        if isinstance(filter, str):
+            f = etree.fromstring(filter)
+            sub_elem = f.find(util.nspath("Filter", FES_NAMESPACE))
+        else:
+            sub_elem = filter
+
         self._query.append(sub_elem)
 
     def set_maxfeatures(self, maxfeatures):


=====================================
owslib/fes2.py
=====================================
@@ -18,6 +18,7 @@ Supports version 2.0.2 (09-026r2).
 from owslib.etree import etree
 from owslib import util
 from owslib.namespaces import Namespaces
+from abc import ABCMeta, abstractmethod
 
 
 # default variables
@@ -391,6 +392,64 @@ class BBox(OgcExpression):
         return tmp
 
 
+class Filter(OgcExpression):
+    def __init__(self, filter):
+        self.filter = filter
+
+    def toXML(self):
+        node = etree.Element(util.nspath_eval("fes:Filter", namespaces))
+        node.append(self.filter.toXML())
+        return node
+
+
+class TopologicalOpType(OgcExpression, metaclass=ABCMeta):
+    """Abstract base class for topological operators."""
+    @property
+    @abstractmethod
+    def operation(self):
+        """This is a mechanism to ensure this class is subclassed by an actual operation."""
+        pass
+
+    def __init__(self, propertyname, geometry):
+        self.propertyname = propertyname
+        self.geometry = geometry
+
+    def toXML(self):
+        node = etree.Element(util.nspath_eval(f"fes:{self.operation}", namespaces))
+        etree.SubElement(node, util.nspath_eval("fes:ValueReference", namespaces)).text = self.propertyname
+        node.append(self.geometry.toXML())
+
+        return node
+
+
+class Intersects(TopologicalOpType):
+    operation = "Intersects"
+
+
+class Contains(TopologicalOpType):
+    operation = "Contains"
+
+
+class Disjoint(TopologicalOpType):
+    operation = "Disjoint"
+
+
+class Within(TopologicalOpType):
+    operation = "Within"
+
+
+class Touches(TopologicalOpType):
+    operation = "Touches"
+
+
+class Overlaps(TopologicalOpType):
+    operation = "Overlaps"
+
+
+class Equals(TopologicalOpType):
+    operation = "Equals"
+
+
 # BINARY
 class BinaryLogicOpType(OgcExpression):
     """ Binary Operators: And / Or """


=====================================
owslib/gml.py
=====================================
@@ -0,0 +1,69 @@
+from dataclasses import dataclass
+from typing import Sequence
+from owslib.etree import etree
+from owslib import util
+from owslib.namespaces import Namespaces
+
+
+def get_namespaces():
+    n = Namespaces()
+    ns = n.get_namespaces(["gml32", "ogc", "xsd"])
+    ns[None] = n.get_namespace("ogc")
+    return ns
+
+
+namespaces = get_namespaces()
+
+
+def prefix(x):
+    """Shorthand to insert namespaces."""
+    return util.nspath_eval(x, namespaces)
+
+
+ at dataclass
+class AbstractGMLType():
+    id: str
+
+
+ at dataclass
+class AbstractGeometryType(AbstractGMLType):
+    srsName: str = None
+    srsDimension: int = None
+    # axisLabels: str = None
+    # uomLabels: str = None
+
+    description: str = None
+    descriptionReference: str = None
+    identifier: str = None
+    name: str = None
+
+
+ at dataclass
+class _PointBase:
+    """This is used to avoid issues arising from non-optional attributes defined after optional attributes."""
+    pos: Sequence[float]
+
+
+ at dataclass
+class Point(AbstractGeometryType, _PointBase):
+    """GML Point object."""
+
+    def toXML(self):
+        """Return `lxml.etree.Element` object."""
+        node = etree.Element(prefix("gml32:Point"))
+        for key in ["id", "srsName"]:
+            if getattr(self, key, None) is not None:
+                node.set(prefix(f"gml32:{key}"), getattr(self, key))
+
+        for key in ["description", "descriptionReference", "identifier", "name"]:
+            content = getattr(self, key)
+            if content is not None:
+                etree.SubElement(node, prefix(f"gml32:{key}")).text = content
+
+        coords = etree.SubElement(node, prefix("gml32:pos"))
+        coords.text = " ".join([str(c) for c in self.pos])
+        for key in ["srsDimension"]:
+            if getattr(self, key, None) is not None:
+                node.set(prefix(f"gml32:{key}"), getattr(self, key))
+
+        return node


=====================================
owslib/iso.py
=====================================
@@ -373,6 +373,7 @@ class MD_DataIdentification(object):
             self.abstract_url = None
             self.purpose = None
             self.status = None
+            self.graphicoverview = []
             self.contact = []
             self.keywords = []
             self.keywords2 = []
@@ -546,6 +547,14 @@ class MD_DataIdentification(object):
 
             self.status = _testCodeListValue(md.find(util.nspath_eval('gmd:status/gmd:MD_ProgressCode', namespaces)))
 
+            self.graphicoverview = []
+            for val in md.findall(util.nspath_eval(
+                    'gmd:graphicOverview/gmd:MD_BrowseGraphic/gmd:fileName/gco:CharacterString', namespaces)):
+                if val is not None:
+                    val2 = util.testXMLValue(val)
+                    if val2 is not None:
+                        self.graphicoverview.append(val2)
+
             self.contact = []
             for i in md.findall(util.nspath_eval('gmd:pointOfContact/gmd:CI_ResponsibleParty', namespaces)):
                 o = CI_ResponsibleParty(i)


=====================================
owslib/map/wms111.py
=====================================
@@ -259,7 +259,7 @@ class WebMapService_1_1_1(object):
 
         self.request = bind_url(base_url) + data
 
-        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth)
+        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth, headers=self.headers)
 
         # check for service exceptions, and return
         if u.info().get('Content-Type', '').split(';')[0] in ['application/vnd.ogc.se_xml']:
@@ -326,7 +326,7 @@ class WebMapService_1_1_1(object):
 
         self.request = bind_url(base_url) + data
 
-        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth)
+        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth, headers=self.headers)
 
         # check for service exceptions, and return
         if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml':
@@ -601,7 +601,7 @@ class ContentMetadata(AbstractContentMetadata):
                     and metadataUrl['format'].lower() in ['application/xml', 'text/xml']:  # download URL
                 try:
                     content = openURL(
-                        metadataUrl['url'], timeout=timeout, auth=self.auth)
+                        metadataUrl['url'], timeout=timeout, auth=self.auth, headers=self.headers)
                     doc = etree.fromstring(content.read())
 
                     if metadataUrl['type'] == 'FGDC':


=====================================
owslib/map/wms130.py
=====================================
@@ -305,7 +305,7 @@ class WebMapService_1_3_0(object):
 
         self.request = bind_url(base_url) + data
 
-        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth)
+        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth, headers=self.headers)
 
         # need to handle casing in the header keys
         headers = {}
@@ -380,7 +380,7 @@ class WebMapService_1_3_0(object):
 
         self.request = bind_url(base_url) + data
 
-        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth)
+        u = openURL(base_url, data, method, timeout=timeout or self.timeout, auth=self.auth, headers=self.headers)
 
         # check for service exceptions, and return
         if u.info()['Content-Type'] == 'XML':


=====================================
owslib/ogcapi/__init__.py
=====================================
@@ -59,9 +59,11 @@ class API:
 
         if json_ is not None:  # static JSON string
             self.links = json.loads(json_)['links']
+            self.response = json_
         else:
             response = http_get(url, headers=self.headers, auth=self.auth).json()
             self.links = response['links']
+            self.response = response
 
     def api(self) -> dict:
         """
@@ -151,6 +153,7 @@ class API:
         """
 
         url = self._build_url(path)
+        self.request = url
 
         LOGGER.debug('Request: {}'.format(url))
         LOGGER.debug('Params: {}'.format(kwargs))
@@ -163,6 +166,8 @@ class API:
         if response.status_code != requests.codes.ok:
             raise RuntimeError(response.text)
 
+        self.request = response.url
+
         if as_dict:
             return response.json()
         else:


=====================================
owslib/ogcapi/features.py
=====================================
@@ -59,7 +59,7 @@ class Features(Collections):
         """
 
         if 'bbox' in kwargs:
-            kwargs['bbox'] = ','.join(kwargs['bbox'])
+            kwargs['bbox'] = ','.join(list(map(str, kwargs['bbox'])))
 
         path = 'collections/{}/items'.format(collection_id)
         return self._request(path=path, kwargs=kwargs)


=====================================
requirements.txt
=====================================
@@ -3,3 +3,4 @@ pytz
 requests>=1.0
 pyproj >=2
 pyyaml
+dataclasses; python_version < '3.7'


=====================================
tests/test_fes2.py
=====================================
@@ -0,0 +1,46 @@
+import pytest
+from owslib.wfs import WebFeatureService
+from owslib import fes2
+from owslib.gml import Point
+from owslib.namespaces import Namespaces
+from owslib import util
+import json
+
+n = Namespaces()
+FES_NAMESPACE = n.get_namespace("fes")
+GML32_NAMESPACE = n.get_namespace("gml32")
+
+
+SERVICE_URL = "http://soggy.zoology.ubc.ca:8080/geoserver/wfs"
+
+
+def test_raw_filter():
+    """Just inspect the filter object (not embedded in a getfeature request)."""
+    point = Point(id="qc", srsName="http://www.opengis.net/gml/srs/epsg.xml#4326", pos=[-71, 46])
+    f = fes2.Filter(
+        fes2.And([fes2.Intersects(propertyname="the_geom", geometry=point),
+                  fes2.PropertyIsLike("name", "value")]
+                 )
+    )
+
+    xml = f.toXML()
+
+    # Fairly basic test
+    xml.find(util.nspath("Filter", FES_NAMESPACE))
+    xml.find(util.nspath("And", FES_NAMESPACE))
+    xml.find(util.nspath("Intersects", FES_NAMESPACE))
+    xml.find(util.nspath("Point", GML32_NAMESPACE))
+
+
+ at pytest.mark.online
+def test_filter():
+    """A request without filtering will yield 600 entries. With filtering we expect only one.
+
+    Note that this type of topological filtering only works (so far) with WFS 2.0.0 and POST requests.
+    """
+    wfs = WebFeatureService(SERVICE_URL, version="2.0.0")
+    layer = "bcmca:commercialfish_crab"
+    point = Point(id="random", srsName="http://www.opengis.net/gml/srs/epsg.xml#4326", pos=[-129.8, 55.44])
+    f = fes2.Filter(fes2.Contains(propertyname="geom", geometry=point))
+    r = wfs.getfeature(layer, outputFormat="application/json", method="POST", filter=f.toXML())
+    assert json.load(r)["totalFeatures"] == 1


=====================================
tests/test_ogcapi_features_pygeoapi.py
=====================================
@@ -23,7 +23,7 @@ def test_ogcapi_features_pygeoapi():
     assert paths['/collections/lakes'] is not None
 
     conformance = w.conformance()
-    assert len(conformance['conformsTo']) == 17
+    assert len(conformance['conformsTo']) == 16
 
     collections = w.collections()
     assert len(collections) > 0


=====================================
tests/test_ogcapi_records_pygeoapi.py
=====================================
@@ -29,12 +29,15 @@ def test_ogcapi_records_pygeoapi():
     assert len(collections) > 0
 
     records = w.records()
-#    assert len(records) > 0
+    assert len(records['features']) > 0
 
     msc_wis_dcpc = w.collection('discovery-metadata')
     assert msc_wis_dcpc['id'] == 'discovery-metadata'
     assert msc_wis_dcpc['title'] == 'MSC discovery metadata'
     assert msc_wis_dcpc['description'] == 'MSC discovery metadata'
+    assert w.request == 'https://dev.api.weather.gc.ca/msc-wis-dcpc/collections/discovery-metadata'  # noqa
+    assert w.response is not None
+    assert isinstance(w.response, dict)
 
     msc_wis_dcpc_queryables = w.collection_queryables('discovery-metadata')
     assert len(msc_wis_dcpc_queryables['queryables']) == 7


=====================================
tests/test_wfs_generic.py
=====================================
@@ -2,7 +2,7 @@ from owslib.wfs import WebFeatureService
 from owslib.util import ServiceException
 from urllib.parse import urlparse
 from tests.utils import resource_file, sorted_url_query, service_ok
-
+import json
 import pytest
 
 SERVICE_URL = 'https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288'
@@ -54,7 +54,6 @@ def test_getfeature():
 @pytest.mark.skipif(not service_ok(SERVICE_URL),
                     reason="WFS service is unreachable")
 def test_outputformat_wfs_100():
-    import json
     wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
                             version='1.0.0')
     feature = wfs.getfeature(
@@ -66,7 +65,6 @@ def test_outputformat_wfs_100():
 @pytest.mark.skipif(not service_ok(SERVICE_URL),
                     reason="WFS service is unreachable")
 def test_outputformat_wfs_110():
-    import json
     wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
                             version='1.1.0')
     feature = wfs.getfeature(
@@ -78,7 +76,6 @@ def test_outputformat_wfs_110():
 @pytest.mark.skipif(not service_ok(SERVICE_URL),
                     reason="WFS service is unreachable")
 def test_outputformat_wfs_200():
-    import json
     wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
                             version='2.0.0')
     feature = wfs.getfeature(
@@ -90,7 +87,6 @@ def test_outputformat_wfs_200():
 @pytest.mark.skipif(not service_ok(SERVICE_URL),
                     reason="WFS service is unreachable")
 def test_srsname_wfs_100():
-    import json
     wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
                             version='1.0.0')
     # ServiceException: Unable to support srsName: EPSG:99999999
@@ -99,7 +95,6 @@ def test_srsname_wfs_100():
             typename=['sb:Project_Area'], maxfeatures=1, propertyname=None, outputFormat='application/json',
             srsname="EPSG:99999999")
 
-    import json
     wfs = WebFeatureService('https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
                             version='1.0.0')
     feature = wfs.getfeature(
@@ -112,7 +107,6 @@ def test_srsname_wfs_100():
 @pytest.mark.skipif(not service_ok(SERVICE_URL),
                     reason="WFS service is unreachable")
 def test_srsname_wfs_110():
-    import json
     wfs = WebFeatureService(
         'https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
         version='1.1.0')
@@ -122,7 +116,6 @@ def test_srsname_wfs_110():
             typename=['sb:Project_Area'], maxfeatures=1, propertyname=None, outputFormat='application/json',
             srsname="EPSG:99999999")
 
-    import json
     wfs = WebFeatureService(
         'https://www.sciencebase.gov/catalogMaps/mapping/ows/53398e51e4b0db25ad10d288',
         version='1.0.0')


=====================================
tests/test_wms_getmap.py
=====================================
@@ -1,3 +1,5 @@
+from unittest import mock
+
 import pytest
 from tests.utils import service_ok
 
@@ -151,3 +153,22 @@ def test_ncwms2():
     assert "height=256" in wms.request
     assert "format=image%2Fpng" in wms.request
     assert "transparent=TRUE" in wms.request
+
+
+ at pytest.mark.parametrize('wms_version', ['1.1.1', '1.3.0'])
+def test_wms_sends_headers(wms_version):
+    """Test that if headers are provided in the WMS class they are sent
+    when performing HTTP requests (in this case for GetCapabilities)
+    """
+
+    with mock.patch('owslib.util.requests.request', side_effect=RuntimeError) as mock_request:
+        try:
+            WebMapService(
+                'http://example.com/wms',
+                version=wms_version,
+                headers={'User-agent': 'my-app/1.0'}
+            )
+        except RuntimeError:
+
+            assert mock_request.called
+            assert mock_request.call_args[1]['headers'] == {'User-agent': 'my-app/1.0'}



View it on GitLab: https://salsa.debian.org/debian-gis-team/owslib/-/compare/c7d3f98548593197d3283ecfa5b7d5a34c18538b...87a2647014aa8e0fe44fa461926bdce274ef7079

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/owslib/-/compare/c7d3f98548593197d3283ecfa5b7d5a34c18538b...87a2647014aa8e0fe44fa461926bdce274ef7079
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210824/7fd80312/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list