[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