This is an automated email from the git hooks/post-receive script. johanvdw-guest pushed a commit to branch master in repository owslib.
commit 0a6a7e9c30417711d465bc8c4c652eb80a536a71 Author: Johan Van de Wauw <johan.vandew...@gmail.com> Date: Tue Jun 16 20:38:44 2015 +0200 Imported Upstream version 0.9.0 --- OWSLib.egg-info/PKG-INFO | 2 +- OWSLib.egg-info/SOURCES.txt | 1 + OWSLib.egg-info/requires.txt | 1 + PKG-INFO | 2 +- VERSION.txt | 2 +- owslib/__init__.py | 2 +- owslib/coverage/wcs100.py | 5 +- owslib/coverage/wcs110.py | 163 ++++++++++++++++----------- owslib/coverage/wcs111.py | 31 +++++ owslib/coverage/wcsBase.py | 29 ++--- owslib/crs.py | 18 +++ owslib/csw.py | 97 +++++++++------- owslib/etree.py | 25 +++- owslib/feature/__init__.py | 8 +- owslib/feature/wfs100.py | 51 ++++++--- owslib/feature/wfs110.py | 42 ++++--- owslib/feature/wfs200.py | 59 ++++++---- owslib/fes.py | 2 +- owslib/iso.py | 37 +++++- owslib/namespaces.py | 8 +- owslib/swe/common.py | 28 ++--- owslib/swe/observation/sos100.py | 18 ++- owslib/swe/observation/sos200.py | 17 ++- owslib/swe/sensor/sml.py | 2 +- owslib/tms.py | 27 +++-- owslib/util.py | 238 +++++++++++++++++++-------------------- owslib/waterml/wml10.py | 8 +- owslib/waterml/wml11.py | 8 +- owslib/wcs.py | 18 ++- owslib/wms.py | 48 ++++---- owslib/wmts.py | 46 ++++---- owslib/wps.py | 2 +- requirements.txt | 1 + setup.py | 1 + 34 files changed, 627 insertions(+), 420 deletions(-) diff --git a/OWSLib.egg-info/PKG-INFO b/OWSLib.egg-info/PKG-INFO index 5fac362..a6acc29 100644 --- a/OWSLib.egg-info/PKG-INFO +++ b/OWSLib.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: OWSLib -Version: 0.8.13 +Version: 0.9.0 Summary: OGC Web Service utility library Home-page: http://geopython.github.io/OWSLib Author: Tom Kralidis diff --git a/OWSLib.egg-info/SOURCES.txt b/OWSLib.egg-info/SOURCES.txt index 92df1f4..81b7e47 100644 --- a/OWSLib.egg-info/SOURCES.txt +++ b/OWSLib.egg-info/SOURCES.txt @@ -40,6 +40,7 @@ owslib/wps.py owslib/coverage/__init__.py owslib/coverage/wcs100.py owslib/coverage/wcs110.py +owslib/coverage/wcs111.py owslib/coverage/wcsBase.py owslib/coverage/wcsdecoder.py owslib/feature/__init__.py diff --git a/OWSLib.egg-info/requires.txt b/OWSLib.egg-info/requires.txt index 8b6b3b8..508d303 100644 --- a/OWSLib.egg-info/requires.txt +++ b/OWSLib.egg-info/requires.txt @@ -1,2 +1,3 @@ python-dateutil>=1.5 pytz +requests>=2.7 diff --git a/PKG-INFO b/PKG-INFO index 5fac362..a6acc29 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: OWSLib -Version: 0.8.13 +Version: 0.9.0 Summary: OGC Web Service utility library Home-page: http://geopython.github.io/OWSLib Author: Tom Kralidis diff --git a/VERSION.txt b/VERSION.txt index c2f73c6..ac39a10 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.8.13 +0.9.0 diff --git a/owslib/__init__.py b/owslib/__init__.py index 442ef8c..05339c5 100644 --- a/owslib/__init__.py +++ b/owslib/__init__.py @@ -1,3 +1,3 @@ from __future__ import (absolute_import, division, print_function) -__version__ = '0.8.13' +__version__ = '0.9.0' diff --git a/owslib/coverage/wcs100.py b/owslib/coverage/wcs100.py index 110b118..d9ec1f4 100644 --- a/owslib/coverage/wcs100.py +++ b/owslib/coverage/wcs100.py @@ -12,7 +12,10 @@ from __future__ import (absolute_import, division, print_function) from owslib.coverage.wcsBase import WCSBase, WCSCapabilitiesReader, ServiceException -from urllib import urlencode +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode from owslib.util import openURL, testXMLValue from owslib.etree import etree from owslib.crs import Crs diff --git a/owslib/coverage/wcs110.py b/owslib/coverage/wcs110.py index 01a212b..9e2591b 100644 --- a/owslib/coverage/wcs110.py +++ b/owslib/coverage/wcs110.py @@ -15,8 +15,10 @@ from __future__ import (absolute_import, division, print_function) from .wcsBase import WCSBase, WCSCapabilitiesReader, ServiceException from owslib.util import openURL, testXMLValue -from urllib import urlencode -from urllib2 import urlopen +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode from owslib.etree import etree import os, errno from owslib.coverage import wcsdecoder @@ -25,14 +27,26 @@ from owslib.crs import Crs import logging from owslib.util import log -def ns(tag): - return '{http://www.opengis.net/wcs/1.1}'+tag +class Namespaces_1_1_0(): + + def WCS(self, tag): + return '{http://www.opengis.net/wcs/1.1}'+tag + + def WCS_OWS(self, tag): + return '{http://www.opengis.net/wcs/1.1/ows}'+tag + + def OWS(self, tag): + return '{http://www.opengis.net/ows/1.1}'+tag + class WebCoverageService_1_1_0(WCSBase): """Abstraction for OGC Web Coverage Service (WCS), version 1.1.0 Implements IWebCoverageService. """ + version='1.1.0' + ns = Namespaces_1_1_0() + def __getitem__(self, name): ''' check contents dictionary to allow dict like access to service layers''' if name in self.__getattribute__('contents').keys(): @@ -41,7 +55,7 @@ class WebCoverageService_1_1_0(WCSBase): raise KeyError("No content named %s" % name) def __init__(self,url,xml, cookies): - self.version='1.1.0' + self.url = url self.cookies=cookies # initialize from saved capability document or access the server @@ -52,7 +66,7 @@ class WebCoverageService_1_1_0(WCSBase): self._capabilities = reader.read(self.url) # check for exceptions - se = self._capabilities.find('{http://www.opengis.net/ows/1.1}Exception') + se = self._capabilities.find(self.ns.OWS('Exception')) if se is not None: err_message = str(se.text).strip() @@ -61,19 +75,19 @@ class WebCoverageService_1_1_0(WCSBase): #build metadata objects: #serviceIdentification metadata - elem=self._capabilities.find('{http://www.opengis.net/wcs/1.1/ows}ServiceIdentification') + elem=self._capabilities.find(self.ns.WCS_OWS('ServiceIdentification')) if elem is None: - elem=self._capabilities.find('{http://www.opengis.net/ows/1.1}ServiceIdentification') - self.identification=ServiceIdentification(elem) + elem=self._capabilities.find(self.ns.OWS('ServiceIdentification')) + self.identification=ServiceIdentification(elem, self.ns) #serviceProvider - elem=self._capabilities.find('{http://www.opengis.net/ows/1.1}ServiceProvider') - self.provider=ServiceProvider(elem) + elem=self._capabilities.find(self.ns.OWS('ServiceProvider')) or self._capabilities.find(self.ns.OWS('ServiceProvider')) + self.provider=ServiceProvider(elem, self.ns) #serviceOperations self.operations = [] - for elem in self._capabilities.findall('{http://www.opengis.net/wcs/1.1/ows}OperationsMetadata/{http://www.opengis.net/wcs/1.1/ows}Operation/'): - self.operations.append(Operation(elem)) + for elem in self._capabilities.findall(self.ns.WCS_OWS('OperationsMetadata') + '/' + self.ns.WCS_OWS('Operation') + '/'): + self.operations.append(Operation(elem, self.ns)) # exceptions - ***********TO DO ************* self.exceptions = [f.text for f \ @@ -82,16 +96,16 @@ class WebCoverageService_1_1_0(WCSBase): # serviceContents: our assumption is that services use a top-level layer # as a metadata organizer, nothing more. self.contents = {} - top = self._capabilities.find('{http://www.opengis.net/wcs/1.1}Contents/{http://www.opengis.net/wcs/1.1}CoverageSummary') - for elem in self._capabilities.findall('{http://www.opengis.net/wcs/1.1}Contents/{http://www.opengis.net/wcs/1.1}CoverageSummary/{http://www.opengis.net/wcs/1.1}CoverageSummary'): - cm=ContentMetadata(elem, top, self) + top = self._capabilities.find(self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary')) + for elem in self._capabilities.findall(self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary') + '/' + self.ns.WCS('CoverageSummary')): + cm=ContentMetadata(elem, top, self, self.ns) self.contents[cm.id]=cm if self.contents=={}: #non-hierarchical. top=None - for elem in self._capabilities.findall('{http://www.opengis.net/wcs/1.1}Contents/{http://www.opengis.net/wcs/1.1}CoverageSummary'): - cm=ContentMetadata(elem, top, self) + for elem in self._capabilities.findall(self.ns.WCS('Contents') + '/' + self.ns.WCS('CoverageSummary')): + cm=ContentMetadata(elem, top, self, self.ns) #make the describeCoverage requests to populate the supported formats/crs attributes self.contents[cm.id]=cm @@ -138,10 +152,10 @@ class WebCoverageService_1_1_0(WCSBase): if store = false, returns a multipart mime """ if log.isEnabledFor(logging.DEBUG): - log.debug('WCS 1.1.0 DEBUG: Parameters passed to GetCoverage: identifier=%s, bbox=%s, time=%s, format=%s, rangesubset=%s, gridbaseCRS=%s, gridtype=%s, gridCS=%s, gridorigin=%s, gridoffsets=%s, method=%s, other_arguments=%s'%(identifier, bbox, time, format, rangesubset, gridbaseCRS, gridtype, gridCS, gridorigin, gridoffsets, method, str(kwargs))) + log.debug('WCS 1.1.0 DEBUG: Parameters passed to GetCoverage: identifier=%s, bbox=%s, time=%s, format=%s, rangesubset=%s, gridbaseCRS=%s, gridtype=%s, gridCS=%s, gridorigin=%s, gridoffsets=%s, method=%s, other_arguments=%s'%(identifier, bbox, time, format, rangesubset, gridbaseCRS, gridtype, gridCS, gridorigin, gridoffsets, method, str(kwargs))) if method == 'Get': - method='{http://www.opengis.net/wcs/1.1/ows}Get' + method=self.ns.WCS_OWS('Get') try: base_url = next((m.get('url') for m in self.getOperationByName('GetCoverage').methods if m.get('type').lower() == method.lower())) except StopIteration: @@ -199,11 +213,14 @@ class Operation(object): """Abstraction for operation metadata Implements IOperationMetadata. """ - def __init__(self, elem): + ns = Namespaces_1_1_0() + + def __init__(self, elem, nmSpc): + self.name = elem.get('name') - self.formatOptions = [f.text for f in elem.findall('{http://www.opengis.net/wcs/1.1/ows}Parameter/{http://www.opengis.net/wcs/1.1/ows}AllowedValues/{http://www.opengis.net/wcs/1.1/ows}Value')] + self.formatOptions = [f.text for f in elem.findall(nmSpc.WCS_OWS('Parameter') + '/' + nmSpc.WCS_OWS('AllowedValues') + '/' + nmSpc.WCS_OWS('Value'))] methods = [] - for verb in elem.findall('{http://www.opengis.net/wcs/1.1/ows}DCP/{http://www.opengis.net/wcs/1.1/ows}HTTP/*'): + for verb in elem.findall(nmSpc.WCS_OWS('DCP') + '/' + nmSpc.WCS_OWS('HTTP/*')): url = verb.attrib['{http://www.w3.org/1999/xlink}href'] methods.append((verb.tag, {'url': url})) self.methods = dict(methods) @@ -211,86 +228,94 @@ class Operation(object): class ServiceIdentification(object): """ Abstraction for ServiceIdentification Metadata implements IServiceIdentificationMetadata""" - def __init__(self,elem): + + def __init__(self, elem, nmSpc): self.service="WCS" - self.version="1.1.0" - self.title=testXMLValue(elem.find('{http://www.opengis.net/ows}Title')) + + self.title=testXMLValue(elem.find(nmSpc.OWS('Title'))) if self.title is None: #may have used the wcs ows namespace: - self.title=testXMLValue(elem.find('{http://www.opengis.net/wcs/1.1/ows}Title')) + self.title=testXMLValue(elem.find(nmSpc.WCS_OWS('Title'))) + if self.title is None: #may have used the other wcs ows namespace: + self.title=testXMLValue(elem.find(nmSpc.OWS('Title'))) - self.abstract=testXMLValue(elem.find('{http://www.opengis.net/ows}Abstract')) + self.abstract=testXMLValue(elem.find(nmSpc.OWS('Abstract'))) if self.abstract is None:#may have used the wcs ows namespace: - self.abstract=testXMLValue(elem.find('{http://www.opengis.net/wcs/1.1/ows}Abstract')) - if elem.find('{http://www.opengis.net/ows}Abstract') is not None: - self.abstract=elem.find('{http://www.opengis.net/ows}Abstract').text + self.abstract=testXMLValue(elem.find(nmSpc.WCS_OWS('Abstract'))) + if self.title is None: #may have used the other wcs ows namespace: + self.title=testXMLValue(elem.find(nmSpc.OWS('Abstract'))) + if elem.find(nmSpc.OWS('Abstract')) is not None: + self.abstract=elem.find(nmSpc.OWS('Abstract')).text else: self.abstract = None - self.keywords = [f.text for f in elem.findall('{http://www.opengis.net/ows}Keywords/{http://www.opengis.net/ows}Keyword')] - #self.link = elem.find('{http://www.opengis.net/wcs/1.1}Service/{http://www.opengis.net/wcs/1.1}OnlineResource').attrib.get('{http://www.w3.org/1999/xlink}href', '') + self.keywords = [f.text for f in elem.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword'))] + #self.link = elem.find(nmSpc.WCS('Service') + '/' + nmSpc.WCS('OnlineResource')).attrib.get('{http://www.w3.org/1999/xlink}href', '') - if elem.find('{http://www.opengis.net/wcs/1.1/ows}Fees') is not None: - self.fees=elem.find('{http://www.opengis.net/wcs/1.1/ows}Fees').text + if elem.find(nmSpc.WCS_OWS('Fees')) is not None: + self.fees=elem.find(nmSpc.WCS_OWS('Fees')).text else: self.fees=None - if elem.find('{http://www.opengis.net/wcs/1.1/ows}AccessConstraints') is not None: - self.accessConstraints=elem.find('{http://www.opengis.net/wcs/1.1/ows}AccessConstraints').text + if elem.find(nmSpc.WCS_OWS('AccessConstraints')) is not None: + self.accessConstraints=elem.find(nmSpc.WCS_OWS('AccessConstraints')).text else: - self.accessConstraints=None - + self.accessConstraints=None class ServiceProvider(object): """ Abstraction for ServiceProvider metadata implements IServiceProviderMetadata """ - def __init__(self,elem): - name=elem.find('{http://www.opengis.net/ows}ProviderName') + + def __init__(self, elem, nmSpc): + name=elem.find(nmSpc.OWS('ProviderName')) + if name is not None: self.name=name.text else: self.name=None - #self.contact=ServiceContact(elem.find('{http://www.opengis.net/ows}ServiceContact')) - self.contact =ContactMetadata(elem) + + #self.contact=ServiceContact(elem.find(nmSpc.OWS('ServiceContact'))) + self.contact =ContactMetadata(elem, nmSpc) self.url=self.name # no obvious definitive place for url in wcs, repeat provider name? class ContactMetadata(object): ''' implements IContactMetadata''' - def __init__(self, elem): + + def __init__(self, elem, nmSpc): try: - self.name = elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}IndividualName').text + self.name = elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('IndividualName')).text except AttributeError: self.name = None try: - self.organization=elem.find('{http://www.opengis.net/ows}ProviderName').text + self.organization=elem.find(nmSpc.OWS('ProviderName')).text except AttributeError: self.organization = None try: - self.address = elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}DeliveryPoint').text + self.address = elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('DeliveryPoint')).text except AttributeError: self.address = None try: - self.city= elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}City').text + self.city= elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('City')).text except AttributeError: self.city = None try: - self.region= elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}AdministrativeArea').text + self.region= elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('AdministrativeArea')).text except AttributeError: self.region = None try: - self.postcode= elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}PostalCode').text + self.postcode= elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('PostalCode')).text except AttributeError: self.postcode = None try: - self.country= elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}Country').text + self.country= elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('Country')).text except AttributeError: self.country = None try: - self.email = elem.find('{http://www.opengis.net/ows}ServiceContact/{http://www.opengis.net/ows}ContactInfo/{http://www.opengis.net/ows}Address/{http://www.opengis.net/ows}ElectronicMailAddress').text + self.email = elem.find(nmSpc.OWS('ServiceContact') + '/' + nmSpc.OWS('ContactInfo') + '/' + nmSpc.OWS('Address') + '/' + nmSpc.OWS('ElectronicMailAddress')).text except AttributeError: self.email = None @@ -298,36 +323,37 @@ class ContentMetadata(object): """Abstraction for WCS ContentMetadata Implements IContentMetadata """ - def __init__(self, elem, parent, service): + + def __init__(self, elem, parent, service, nmSpc): """Initialize.""" #TODO - examine the parent for bounding box info. - + self._service=service self._elem=elem self._parent=parent - self.id=self._checkChildAndParent('{http://www.opengis.net/wcs/1.1}Identifier') - self.description =self._checkChildAndParent('{http://www.opengis.net/wcs/1.1}Description') - self.title =self._checkChildAndParent('{http://www.opengis.net/ows}Title') - self.abstract =self._checkChildAndParent('{http://www.opengis.net/ows}Abstract') + self.id=self._checkChildAndParent(nmSpc.WCS('Identifier')) + self.description =self._checkChildAndParent(nmSpc.WCS('Description')) + self.title =self._checkChildAndParent(nmSpc.OWS('Title')) + self.abstract =self._checkChildAndParent(nmSpc.OWS('Abstract')) #keywords. self.keywords=[] - for kw in elem.findall('{http://www.opengis.net/ows}Keywords/{http://www.opengis.net/ows}Keyword'): + for kw in elem.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword')): if kw is not None: self.keywords.append(kw.text) #also inherit any keywords from parent coverage summary (if there is one) if parent is not None: - for kw in parent.findall('{http://www.opengis.net/ows}Keywords/{http://www.opengis.net/ows}Keyword'): + for kw in parent.findall(nmSpc.OWS('Keywords') + '/' + nmSpc.OWS('Keyword')): if kw is not None: self.keywords.append(kw.text) self.boundingBox=None #needed for iContentMetadata harmonisation self.boundingBoxWGS84 = None - b = elem.find('{http://www.opengis.net/ows}WGS84BoundingBox') + b = elem.find(nmSpc.OWS('WGS84BoundingBox')) if b is not None: - lc=b.find('{http://www.opengis.net/ows}LowerCorner').text - uc=b.find('{http://www.opengis.net/ows}UpperCorner').text + lc=b.find(nmSpc.OWS('LowerCorner')).text + uc=b.find(nmSpc.OWS('UpperCorner')).text self.boundingBoxWGS84 = ( float(lc.split()[0]),float(lc.split()[1]), float(uc.split()[0]), float(uc.split()[1]), @@ -335,11 +361,11 @@ class ContentMetadata(object): # bboxes - other CRS self.boundingboxes = [] - for bbox in elem.findall('{http://www.opengis.net/ows}BoundingBox'): + for bbox in elem.findall(nmSpc.OWS('BoundingBox')): if bbox is not None: try: - lc=b.find('{http://www.opengis.net/ows}LowerCorner').text - uc=b.find('{http://www.opengis.net/ows}UpperCorner').text + lc=b.find(nmSpc.OWS('LowerCorner')).text + uc=b.find(nmSpc.OWS('UpperCorner')).text boundingBox = ( float(lc.split()[0]),float(lc.split()[1]), float(uc.split()[0]), float(uc.split()[1]), @@ -354,13 +380,13 @@ class ContentMetadata(object): #SupportedCRS self.supportedCRS=[] - for crs in elem.findall('{http://www.opengis.net/wcs/1.1}SupportedCRS'): + for crs in elem.findall(nmSpc.WCS('SupportedCRS')): self.supportedCRS.append(Crs(crs.text)) #SupportedFormats self.supportedFormats=[] - for format in elem.findall('{http://www.opengis.net/wcs/1.1}SupportedFormat'): + for format in elem.findall(nmSpc.WCS('SupportedFormat')): self.supportedFormats.append(format.text) #grid is either a gml:Grid or a gml:RectifiedGrid if supplied as part of the DescribeCoverage response. @@ -404,3 +430,4 @@ class ContentMetadata(object): except: value = None return value + diff --git a/owslib/coverage/wcs111.py b/owslib/coverage/wcs111.py new file mode 100644 index 0000000..63034c5 --- /dev/null +++ b/owslib/coverage/wcs111.py @@ -0,0 +1,31 @@ +# -*- coding: ISO-8859-15 -*- +# ============================================================================= +# Copyright (c) 2015 Luís de Sousa +# +# Authors : +# Luís de Sousa <luis.a.de.so...@gmail.com> +# +# Contact email: luis.a.de.so...@gmail.com +# ============================================================================= + +from owslib.coverage import wcs110 + +class Namespaces_1_1_1(): + + def WCS(self, tag): + return '{http://www.opengis.net/wcs/1.1.1}'+tag + + def WCS_OWS(self, tag): + return '{http://www.opengis.net/wcs/1.1.1/ows}'+tag + + def OWS(self, tag): + return '{http://www.opengis.net/ows/1.1}'+tag + + +class WebCoverageService_1_1_1(wcs110.WebCoverageService_1_1_0): + """Abstraction for OGC Web Coverage Service (WCS), version 1.1.1 + Implements IWebCoverageService. + """ + version='1.1.1' + ns = Namespaces_1_1_1() + diff --git a/owslib/coverage/wcsBase.py b/owslib/coverage/wcsBase.py index f49c640..29ea2d3 100644 --- a/owslib/coverage/wcsBase.py +++ b/owslib/coverage/wcsBase.py @@ -11,11 +11,15 @@ from __future__ import (absolute_import, division, print_function) -from urllib import urlencode -from urllib2 import urlopen, Request +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode from owslib.etree import etree import cgi -from StringIO import StringIO +from six.moves import cStringIO as StringIO +import six +from owslib.util import openURL class ServiceException(Exception): @@ -108,19 +112,14 @@ class WCSCapabilitiesReader(object): @return: An elementtree tree representation of the capabilities document """ request = self.capabilities_url(service_url) - req = Request(request) - if self.cookies is not None: - req.add_header('Cookie', self.cookies) - u = urlopen(req, timeout=timeout) + u = openURL(request, timeout=timeout, cookies=self.cookies) return etree.fromstring(u.read()) - + def readString(self, st): """Parse a WCS capabilities document, returning an instance of WCSCapabilitiesInfoset string should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) return etree.fromstring(st) class DescribeCoverageReader(object): @@ -159,7 +158,7 @@ class DescribeCoverageReader(object): if self.version == '1.0.0': if 'coverage' not in params: qs.append(('coverage', self.identifier)) - elif self.version == '1.1.0': + elif self.version == '1.1.0' or self.version == '1.1.1': #NOTE: WCS 1.1.0 is ambigous about whether it should be identifier #or identifiers (see tables 9, 10 of specification) if 'identifiers' not in params: @@ -181,10 +180,6 @@ class DescribeCoverageReader(object): @return: An elementtree tree representation of the capabilities document """ request = self.descCov_url(service_url) - req = Request(request) - if self.cookies is not None: - req.add_header('Cookie', self.cookies) - u = urlopen(req, timeout=timeout) + u = openURL(request, cookies=self.cookies, timeout=timeout) return etree.fromstring(u.read()) - - + diff --git a/owslib/crs.py b/owslib/crs.py index cd55358..c99f7a6 100644 --- a/owslib/crs.py +++ b/owslib/crs.py @@ -1815,6 +1815,24 @@ class Crs(object): (self.version or ""), (self.code or "")) + def getcodeuri1(self): + """Create for example "http://www.opengis.net/def/crs/EPSG/0/4326" + string and return back + + :returns: String code formated in "http://www.opengis.net/def/crs/EPSG/0/code" + """ + + return 'http://www.opengis.net/def/crs/EPSG/0/%s' % self.code + + def getcodeuri2(self): + """Create for example "http://www.opengis.net/gml/srs/epsg.xml#4326" + string and return back + + :returns: String code formated in "http://www.opengis.net/gml/srs/epsg.xml#code" + """ + + return 'http://www.opengis.net/gml/srs/epsg.xml#%s' % self.code + def __eq__(self, other): if isinstance(other, self.__class__): return self.getcodeurn() == other.getcodeurn() diff --git a/owslib/csw.py b/owslib/csw.py index 0d800c8..ed52686 100644 --- a/owslib/csw.py +++ b/owslib/csw.py @@ -11,13 +11,18 @@ from __future__ import (absolute_import, division, print_function) -import base64 import inspect import warnings -import StringIO +import six +try: + from StringIO import StringIO as BytesIO # Python 2 +except ImportError: + from io import BytesIO # Python 3 import random -from urllib import urlencode -from urllib2 import Request, urlopen +try: # Python 3 + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode from owslib.util import OrderedDict @@ -29,7 +34,7 @@ from owslib.iso import MD_Metadata from owslib.fgdc import Metadata from owslib.dif import DIF from owslib.namespaces import Namespaces -from owslib.util import cleanup_namespaces, bind_url, add_namespaces +from owslib.util import cleanup_namespaces, bind_url, add_namespaces, openURL # default variables outputformat = 'application/xml' @@ -41,7 +46,7 @@ namespaces = get_namespaces() schema = 'http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd' schema_location = '%s %s' % (namespaces['csw'], schema) -class CatalogueServiceWeb: +class CatalogueServiceWeb(object): """ csw request class """ def __init__(self, url, lang='en-US', version='2.0.2', timeout=10, skip_caps=False, username=None, password=None): @@ -77,25 +82,31 @@ class CatalogueServiceWeb: data = {'service': self.service, 'version': self.version, 'request': 'GetCapabilities'} - self.request = '%s%s' % (bind_url(self.url), urlencode(data)) + self.request = urlencode(data) self._invoke() if self.exceptionreport is None: # ServiceIdentification val = self._exml.find(util.nspath_eval('ows:ServiceIdentification', namespaces)) - self.identification=ows.ServiceIdentification(val,self.owscommon.namespace) + if val is not None: + self.identification = ows.ServiceIdentification(val,self.owscommon.namespace) + else: + self.identification = None # ServiceProvider val = self._exml.find(util.nspath_eval('ows:ServiceProvider', namespaces)) - self.provider=ows.ServiceProvider(val,self.owscommon.namespace) + if val is not None: + self.provider = ows.ServiceProvider(val,self.owscommon.namespace) + else: + self.provider = None # ServiceOperations metadata - self.operations=[] + self.operations = [] for elem in self._exml.findall(util.nspath_eval('ows:OperationsMetadata/ows:Operation', namespaces)): self.operations.append(ows.OperationsMetadata(elem, self.owscommon.namespace)) # FilterCapabilities val = self._exml.find(util.nspath_eval('ogc:Filter_Capabilities', namespaces)) - self.filters=fes.FilterCapabilities(val) + self.filters = fes.FilterCapabilities(val) def describerecord(self, typename='csw:Record', format=outputformat): """ @@ -276,7 +287,7 @@ class CatalogueServiceWeb: 'id': ','.join(id), } - self.request = '%s%s' % (bind_url(self.url), urlencode(data)) + self.request = urlencode(data) self._invoke() @@ -504,7 +515,7 @@ class CatalogueServiceWeb: """ urls=[] - for key,rec in self.records.iteritems(): + for key,rec in six.iteritems(self.records): #create a generator object, and iterate through it until the match is found #if not found, gets the default value (here "none") url = next((d['url'] for d in rec.references if d['scheme'] == service_string), None) @@ -592,35 +603,43 @@ class CatalogueServiceWeb: def _invoke(self): # do HTTP request - if isinstance(self.request, basestring): # GET KVP - req = Request(self.request) - if self.username is not None and self.password is not None: - base64string = base64.encodestring('%s:%s' % (self.username, self.password))[:-1] - req.add_header('Authorization', 'Basic %s' % base64string) - self.response = urlopen(req, timeout=self.timeout).read() - else: - xml_post_url = self.url - # Get correct POST URL based on Operation list. - # If skip_caps=True, then self.operations has not been set, so use - # default URL. - if hasattr(self, 'operations'): - caller = inspect.stack()[1][3] - if caller == 'getrecords2': caller = 'getrecords' - try: - op = self.get_operation_by_name(caller) - post_verbs = filter(lambda x: x.get('type').lower() == 'post', op.methods) + request_url = self.url + + # Get correct URL based on Operation list. + + # If skip_caps=True, then self.operations has not been set, so use + # default URL. + if hasattr(self, 'operations'): + caller = inspect.stack()[1][3] + if caller == 'getrecords2': caller = 'getrecords' + try: + op = self.get_operation_by_name(caller) + if isinstance(self.request, six.string_types): # GET KVP + get_verbs = [x for x in op.methods if x.get('type').lower() == 'get'] + request_url = get_verbs[0].get('url') + else: + post_verbs = [x for x in op.methods if x.get('type').lower() == 'post'] if len(post_verbs) > 1: # Filter by constraints. We must match a PostEncoding of "XML" - try: - xml_post_url = next(x for x in filter(list, ([pv.get('url') for const in pv.get('constraints') if const.name.lower() == "postencoding" and 'xml' in map(lambda x: x.lower(), const.values)] for pv in post_verbs)))[0] - except StopIteration: + for pv in post_verbs: + for const in pv.get('constraints'): + if const.name.lower() == 'postencoding': + values = [v.lower() for v in const.values] + if 'xml' in values: + request_url = pv.get('url') + break + else: # Well, just use the first one. - xml_post_url = post_verbs[0].get('url') + request_url = post_verbs[0].get('url') elif len(post_verbs) == 1: - xml_post_url = post_verbs[0].get('url') - except: # no such luck, just go with xml_post_url - pass + request_post_url = post_verbs[0].get('url') + except: # no such luck, just go with request_url + pass + if isinstance(self.request, six.string_types): # GET KVP + self.request = '%s%s' % (bind_url(request_url), self.request) + self.response = openURL(self.request, None, 'Get', username=self.username, password=self.password, timeout=self.timeout).read() + else: self.request = cleanup_namespaces(self.request) # Add any namespaces used in the "typeNames" attribute of the # csw:Query element to the query's xml namespaces. @@ -634,10 +653,10 @@ class CatalogueServiceWeb: self.request = util.element_to_string(self.request, encoding='utf-8') - self.response = util.http_post(xml_post_url, self.request, self.lang, self.timeout, self.username, self.password) + self.response = util.http_post(request_url, self.request, self.lang, self.timeout, self.username, self.password) # parse result see if it's XML - self._exml = etree.parse(StringIO.StringIO(self.response)) + self._exml = etree.parse(BytesIO(self.response)) # it's XML. Attempt to decipher whether the XML response is CSW-ish """ valid_xpaths = [ diff --git a/owslib/etree.py b/owslib/etree.py index 70d766d..74bd835 100644 --- a/owslib/etree.py +++ b/owslib/etree.py @@ -5,7 +5,8 @@ # ============================================================================= from __future__ import (absolute_import, division, print_function) - +import six +import inspect def patch_well_known_namespaces(etree_module): @@ -28,20 +29,36 @@ def patch_well_known_namespaces(etree_module): pass warnings.warn("Only 'lxml.etree' >= 2.3 and 'xml.etree.ElementTree' >= 1.3 are fully supported!") - for k, v in ns.get_namespaces().iteritems(): + for k, v in six.iteritems(ns.get_namespaces()): register_namespace(k, v) # try to find lxml or elementtree try: - from lxml import etree + from lxml import etree, ParseError + ElementType = etree._Element except ImportError: try: - # Python 2.5 with ElementTree included + # Python 2.x/3.x with ElementTree included import xml.etree.ElementTree as etree + + try: + from xml.etree.ElementTree import ParseError + except ImportError: + from xml.parsers.expat import ExpatError as ParseError + + if hasattr(etree, 'Element') and inspect.isclass(etree.Element): + # python 3.4, 3.3, 2.7 + ElementType = etree.Element + else: + # python 2.6 + ElementType = etree._ElementInterface + except ImportError: try: # Python < 2.5 with ElementTree installed import elementtree.ElementTree as etree + ParseError = StandardError # i can't find a ParseError related item in elementtree docs! + ElementType = etree.Element except ImportError: raise RuntimeError('You need either lxml or ElementTree to use OWSLib!') diff --git a/owslib/feature/__init__.py b/owslib/feature/__init__.py index b43a11a..2082e3f 100644 --- a/owslib/feature/__init__.py +++ b/owslib/feature/__init__.py @@ -9,7 +9,10 @@ from __future__ import (absolute_import, division, print_function) from owslib.crs import Crs -from urllib import urlencode +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode import logging from owslib.util import log @@ -77,7 +80,7 @@ class WebFeatureService_(object): return None def getGETGetFeatureRequest(self, typename=None, filter=None, bbox=None, featureid=None, - featureversion=None, propertyname=None, maxfeatures=None,storedQueryID=None, storedQueryParams={}, + featureversion=None, propertyname=None, maxfeatures=None,storedQueryID=None, storedQueryParams=None, outputFormat=None, method='Get', startindex=None): """Formulate proper GetFeature request using KVP encoding ---------- @@ -108,6 +111,7 @@ class WebFeatureService_(object): 2) typename and filter (==query) (more expressive) 3) featureid (direct access to known features) """ + storedQueryParams = storedQueryParams or {} base_url = next((m.get('url') for m in self.getOperationByName('GetFeature').methods if m.get('type').lower() == method.lower())) base_url = base_url if base_url.endswith("?") else base_url+"?" diff --git a/owslib/feature/wfs100.py b/owslib/feature/wfs100.py index 619834d..03b6ae0 100644 --- a/owslib/feature/wfs100.py +++ b/owslib/feature/wfs100.py @@ -9,9 +9,12 @@ from __future__ import (absolute_import, division, print_function) import cgi -from cStringIO import StringIO -from urllib import urlencode -from urllib2 import urlopen +from six import PY2 +from six.moves import cStringIO as StringIO +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode from owslib.util import openURL, testXMLValue, extract_xml_list, ServiceException, xmltag_split from owslib.etree import etree from owslib.fgdc import Metadata @@ -122,7 +125,7 @@ class WebFeatureService_1_0_0(object): file-like object. NOTE: this is effectively redundant now""" reader = WFSCapabilitiesReader(self.version) - return urlopen(reader.capabilities_url(self.url), timeout=self.timeout) + return openURL(reader.capabilities_url(self.url), timeout=self.timeout) def items(self): '''supports dict-like items() access''' @@ -130,9 +133,20 @@ class WebFeatureService_1_0_0(object): for item in self.contents: items.append((item,self.contents[item])) return items - + + def _makeStringIO(self, strval): + """ + Helper method to make sure the StringIO being returned will work. + + Differences between Python 2.6/2.7/3.x mean we have a lot of cases to handle. + """ + if PY2: + return StringIO(strval) + + return StringIO(strval.decode()) + def getfeature(self, typename=None, filter=None, bbox=None, featureid=None, - featureversion=None, propertyname=['*'], maxfeatures=None, + featureversion=None, propertyname='*', maxfeatures=None, srsname=None, outputFormat=None, method='{http://www.opengis.net/wfs}Get', startindex=None): """Request and return feature data as a file-like object. @@ -189,8 +203,11 @@ class WebFeatureService_1_0_0(object): assert len(typename) > 0 request['typename'] = ','.join(typename) - if propertyname: + if propertyname is not None: + if not isinstance(propertyname, list): + propertyname = [propertyname] request['propertyname'] = ','.join(propertyname) + if featureversion: request['featureversion'] = str(featureversion) if maxfeatures: request['maxfeatures'] = str(maxfeatures) if startindex: request['startindex'] = str(startindex) @@ -207,14 +224,14 @@ class WebFeatureService_1_0_0(object): # We're going to assume that anything with a content-length > 32k # is data. We'll check anything smaller. - try: + if 'Content-Length' in u.info(): length = int(u.info()['Content-Length']) have_read = False - except (KeyError, AttributeError): + else: data = u.read() have_read = True length = len(data) - + if length < 32000: if not have_read: data = u.read() @@ -223,16 +240,16 @@ class WebFeatureService_1_0_0(object): tree = etree.fromstring(data) except BaseException: # Not XML - return StringIO(data) + return self._makeStringIO(data) else: if tree.tag == "{%s}ServiceExceptionReport" % OGC_NAMESPACE: se = tree.find(nspath('ServiceException', OGC_NAMESPACE)) raise ServiceException(str(se.text).strip()) else: - return StringIO(data) + return self._makeStringIO(data) else: if have_read: - return StringIO(data) + return self._makeStringIO(data) return u def getOperationByName(self, name): @@ -316,7 +333,7 @@ class ContentMetadata: if metadataUrl['url'] is not None and parse_remote_metadata: # download URL try: - content = urlopen(metadataUrl['url'], timeout=timeout) + content = openURL(metadataUrl['url'], timeout=timeout) doc = etree.parse(content) if metadataUrl['type'] is not None: if metadataUrl['type'] == 'FGDC': @@ -385,7 +402,7 @@ class WFSCapabilitiesReader(object): A timeout value (in seconds) for the request. """ request = self.capabilities_url(url) - u = urlopen(request, timeout=timeout) + u = openURL(request, timeout=timeout) return etree.fromstring(u.read()) def readString(self, st): @@ -394,7 +411,7 @@ class WFSCapabilitiesReader(object): string should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, str) and not isinstance(st, bytes): + raise ValueError("String must be of type string or bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/feature/wfs110.py b/owslib/feature/wfs110.py index b5f693a..5dd9e4e 100644 --- a/owslib/feature/wfs110.py +++ b/owslib/feature/wfs110.py @@ -10,9 +10,12 @@ from __future__ import (absolute_import, division, print_function) import cgi -from cStringIO import StringIO -from urllib import urlencode -from urllib2 import urlopen +from six import PY2 +from six.moves import cStringIO as StringIO +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode from owslib.util import openURL, testXMLValue, nspath_eval, ServiceException from owslib.etree import etree from owslib.fgdc import Metadata @@ -108,7 +111,7 @@ class WebFeatureService_1_1_0(WebFeatureService_): file-like object. NOTE: this is effectively redundant now""" reader = WFSCapabilitiesReader(self.version) - return urlopen(reader.capabilities_url(self.url), timeout=self.timeout) + return openURL(reader.capabilities_url(self.url), timeout=self.timeout) def items(self): '''supports dict-like items() access''' @@ -117,8 +120,19 @@ class WebFeatureService_1_1_0(WebFeatureService_): items.append((item,self.contents[item])) return items + def _makeStringIO(self, strval): + """ + Helper method to make sure the StringIO being returned will work. + + Differences between Python 2.6/2.7/3.x mean we have a lot of cases to handle. + """ + if PY2: + return StringIO(strval) + + return StringIO(strval.decode()) + def getfeature(self, typename=None, filter=None, bbox=None, featureid=None, - featureversion=None, propertyname=['*'], maxfeatures=None, + featureversion=None, propertyname='*', maxfeatures=None, srsname=None, outputFormat=None, method='Get', startindex=None): """Request and return feature data as a file-like object. @@ -213,10 +227,10 @@ class WebFeatureService_1_1_0(WebFeatureService_): # check for service exceptions, rewrap, and return # We're going to assume that anything with a content-length > 32k # is data. We'll check anything smaller. - try: + if 'Content-Length' in u.info(): length = int(u.info()['Content-Length']) have_read = False - except (KeyError, AttributeError): + else: data = u.read() have_read = True length = len(data) @@ -229,16 +243,16 @@ class WebFeatureService_1_1_0(WebFeatureService_): tree = etree.fromstring(data) except BaseException: # Not XML - return StringIO(data) + return self._makeStringIO(data) else: if tree.tag == "{%s}ServiceExceptionReport" % namespaces["ogc"]: se = tree.find(nspath_eval('ServiceException', namespaces["ogc"])) raise ServiceException(str(se.text).strip()) else: - return StringIO(data) + return self._makeStringIO(data) else: if have_read: - return StringIO(data) + return self._makeStringIO(data) return u def getOperationByName(self, name): @@ -294,7 +308,7 @@ class ContentMetadata: if metadataUrl['url'] is not None and parse_remote_metadata: # download URL try: - content = urlopen(metadataUrl['url'], timeout=timeout) + content = openURL(metadataUrl['url'], timeout=timeout) doc = etree.parse(content) if metadataUrl['type'] is not None: if metadataUrl['type'] == 'FGDC': @@ -351,7 +365,7 @@ class WFSCapabilitiesReader(object): A timeout value (in seconds) for the request. """ request = self.capabilities_url(url) - u = urlopen(request, timeout=timeout) + u = openURL(request, timeout=timeout) return etree.fromstring(u.read()) def readString(self, st): @@ -360,7 +374,7 @@ class WFSCapabilitiesReader(object): string should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, str) and not isinstance(st, bytes): + raise ValueError("String must be of type string or bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/feature/wfs200.py b/owslib/feature/wfs200.py index b6d9c1e..9a39966 100644 --- a/owslib/feature/wfs200.py +++ b/owslib/feature/wfs200.py @@ -11,16 +11,19 @@ from __future__ import (absolute_import, division, print_function) #owslib imports: from owslib.ows import ServiceIdentification, ServiceProvider, OperationsMetadata from owslib.etree import etree -from owslib.util import nspath, testXMLValue +from owslib.util import nspath, testXMLValue, openURL from owslib.crs import Crs from owslib.feature import WebFeatureService_ from owslib.namespaces import Namespaces #other imports import cgi -from cStringIO import StringIO -from urllib import urlencode -from urllib2 import urlopen +from six import PY2 +from six.moves import cStringIO as StringIO +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode import logging from owslib.util import log @@ -128,7 +131,7 @@ class WebFeatureService_2_0_0(WebFeatureService_): file-like object. NOTE: this is effectively redundant now""" reader = WFSCapabilitiesReader(self.version) - return urlopen(reader.capabilities_url(self.url), timeout=self.timeout) + return openURL(reader.capabilities_url(self.url), timeout=self.timeout) def items(self): '''supports dict-like items() access''' @@ -136,9 +139,20 @@ class WebFeatureService_2_0_0(WebFeatureService_): for item in self.contents: items.append((item,self.contents[item])) return items - + + def _makeStringIO(self, strval): + """ + Helper method to make sure the StringIO being returned will work. + + Differences between Python 2.6/2.7/3.x mean we have a lot of cases to handle. + """ + if PY2: + return StringIO(strval) + + return StringIO(strval.decode()) + def getfeature(self, typename=None, filter=None, bbox=None, featureid=None, - featureversion=None, propertyname=None, maxfeatures=None,storedQueryID=None, storedQueryParams={}, + featureversion=None, propertyname=None, maxfeatures=None,storedQueryID=None, storedQueryParams=None, method='Get', outputFormat=None, startindex=None): """Request and return feature data as a file-like object. #TODO: NOTE: have changed property name from ['*'] to None - check the use of this in WFS 2.0 @@ -171,6 +185,7 @@ class WebFeatureService_2_0_0(WebFeatureService_): 2) typename and filter (==query) (more expressive) 3) featureid (direct access to known features) """ + storedQueryParams = storedQueryParams or {} url = data = None if typename and type(typename) == type(""): typename = [typename] @@ -186,19 +201,19 @@ class WebFeatureService_2_0_0(WebFeatureService_): # If method is 'Post', data will be None here - u = urlopen(url, data, self.timeout) - + u = openURL(url, data, method, timeout=self.timeout) + # check for service exceptions, rewrap, and return # We're going to assume that anything with a content-length > 32k # is data. We'll check anything smaller. - try: + if 'Content-Length' in u.info(): length = int(u.info()['Content-Length']) have_read = False - except KeyError: + else: data = u.read() have_read = True length = len(data) - + if length < 32000: if not have_read: data = u.read() @@ -207,16 +222,16 @@ class WebFeatureService_2_0_0(WebFeatureService_): tree = etree.fromstring(data) except BaseException: # Not XML - return StringIO(data) + return self._makeStringIO(data) else: if tree.tag == "{%s}ServiceExceptionReport" % OGC_NAMESPACE: se = tree.find(nspath('ServiceException', OGC_NAMESPACE)) raise ServiceException(str(se.text).strip()) else: - return StringIO(data) + return self._makeStringIO(data) else: if have_read: - return StringIO(data) + return self._makeStringIO(data) return u @@ -239,7 +254,7 @@ class WebFeatureService_2_0_0(WebFeatureService_): for kw in kwargs.keys(): request[kw]=str(kwargs[kw]) encoded_request=urlencode(request) - u = urlopen(base_url + encoded_request) + u = openURL(base_url + encoded_request) return u.read() @@ -258,7 +273,7 @@ class WebFeatureService_2_0_0(WebFeatureService_): request = {'service': 'WFS', 'version': self.version, 'request': 'ListStoredQueries'} encoded_request = urlencode(request) - u = urlopen(base_url, data=encoded_request, timeout=self.timeout) + u = openURL(base_url, data=encoded_request, timeout=self.timeout) tree=etree.fromstring(u.read()) tempdict={} for sqelem in tree[:]: @@ -278,7 +293,7 @@ class WebFeatureService_2_0_0(WebFeatureService_): base_url = self.url request = {'service': 'WFS', 'version': self.version, 'request': 'DescribeStoredQueries'} encoded_request = urlencode(request) - u = urlopen(base_url, data=encoded_request, timeout=to) + u = openURL(base_url, data=encoded_request, timeout=to) tree=etree.fromstring(u.read()) tempdict2={} for sqelem in tree[:]: @@ -386,7 +401,7 @@ class ContentMetadata: if metadataUrl['url'] is not None and parse_remote_metadata: # download URL try: - content = urllib2.urlopen(metadataUrl['url'], timeout=timeout) + content = openURL(metadataUrl['url'], timeout=timeout) doc = etree.parse(content) try: # FGDC metadataUrl['metadata'] = Metadata(doc) @@ -438,7 +453,7 @@ class WFSCapabilitiesReader(object): A timeout value (in seconds) for the request. """ request = self.capabilities_url(url) - u = urlopen(request, timeout=timeout) + u = openURL(request, timeout=timeout) return etree.fromstring(u.read()) def readString(self, st): @@ -447,6 +462,6 @@ class WFSCapabilitiesReader(object): string should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, str) and not isinstance(st, bytes): + raise ValueError("String must be of type string or bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/fes.py b/owslib/fes.py index 9caed02..e944635 100644 --- a/owslib/fes.py +++ b/owslib/fes.py @@ -98,7 +98,7 @@ class FilterRequest(object): # And together filters if more than one exists - filters = filter(None,[keyword_filter, bbox_filter, dc_type_equals_filter]) + filters = [_f for _f in [keyword_filter, bbox_filter, dc_type_equals_filter] if _f] if len(filters) == 1: self._root.append(filters[0].toXML()) elif len(filters) > 1: diff --git a/owslib/iso.py b/owslib/iso.py index 6c294d0..adc7e96 100644 --- a/owslib/iso.py +++ b/owslib/iso.py @@ -541,6 +541,9 @@ class SV_ServiceIdentification(object): """ process SV_ServiceIdentification """ def __init__(self, md=None): if md is None: + self.title = None + self.abstract = None + self.contact = None self.identtype = 'service' self.type = None self.version = None @@ -552,6 +555,15 @@ class SV_ServiceIdentification(object): else: val=md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString', namespaces)) self.title=util.testXMLValue(val) + + val=md.find(util.nspath_eval('gmd:abstract/gco:CharacterString', namespaces)) + self.abstract=util.testXMLValue(val) + + self.contact = None + val = md.find(util.nspath_eval('gmd:citation/gmd:CI_Citation/gmd:citedResponsibleParty/gmd:CI_ResponsibleParty', namespaces)) + if val is not None: + self.contact = CI_ResponsibleParty(val) + self.identtype = 'service' val = md.find(util.nspath_eval('srv:serviceType/gco:LocalName', namespaces)) self.type = util.testXMLValue(val) @@ -705,12 +717,29 @@ class EX_Extent(object): class MD_ReferenceSystem(object): """ process MD_ReferenceSystem """ - def __init__(self, md): + def __init__(self, md=None): if md is None: - pass + self.code = None + self.codeSpace = None + self.version = None else: val = md.find(util.nspath_eval('gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:code/gco:CharacterString', namespaces)) - self.code = util.testXMLValue(val) + if val is not None: + self.code = util.testXMLValue(val) + else: + self.code = None + + val = md.find(util.nspath_eval('gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:codeSpace/gco:CharacterString', namespaces)) + if val is not None: + self.codeSpace = util.testXMLValue(val) + else: + self.codeSpace = None + + val = md.find(util.nspath_eval('gmd:referenceSystemIdentifier/gmd:RS_Identifier/gmd:version/gco:CharacterString', namespaces)) + if val is not None: + self.version = util.testXMLValue(val) + else: + self.version = None def _testCodeListValue(elpath): """ get gco:CodeListValue_Type attribute, else get text content """ @@ -761,7 +790,7 @@ class CodelistCatalogue(object): self.dictionaries[id]['entries'][id2]['codespace'] = util.testXMLValue(val, True) def getcodelistdictionaries(self): - return self.dictionaries.keys() + return list(self.dictionaries.keys()) def getcodedefinitionidentifiers(self, cdl): if cdl in self.dictionaries: diff --git a/owslib/namespaces.py b/owslib/namespaces.py index 5ee49b1..2ab248f 100644 --- a/owslib/namespaces.py +++ b/owslib/namespaces.py @@ -1,4 +1,5 @@ from __future__ import (absolute_import, division, print_function) +import six class Namespaces(object): @@ -35,6 +36,7 @@ class Namespaces(object): 'ows200': 'http://www.opengis.net/ows/2.0', 'rim' : 'urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0', 'rdf' : 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'sa' : 'http://www.opengis.net/sampling/1.0', 'sml' : 'http://www.opengis.net/sensorML/1.0.1', 'sml101': 'http://www.opengis.net/sensorML/1.0.1', 'sos' : 'http://www.opengis.net/sos/1.0', @@ -72,7 +74,7 @@ class Namespaces(object): 'http://www.opengis.net/wfs/2.0' """ retval = None - if key in self.namespace_dict.keys(): + if key in self.namespace_dict: retval = self.namespace_dict[key] return retval @@ -102,7 +104,7 @@ class Namespaces(object): key += version retval = None - if key in self.namespace_dict.keys(): + if key in self.namespace_dict: retval = self.namespace_dict[key] return retval @@ -131,7 +133,7 @@ class Namespaces(object): if keys is None or len(keys) == 0: return self.namespace_dict - if isinstance(keys, unicode) or isinstance(keys, str): + if isinstance(keys, six.string_types): return { keys: self.get_namespace(keys) } retval = {} diff --git a/owslib/swe/common.py b/owslib/swe/common.py index bee32ac..ea11064 100644 --- a/owslib/swe/common.py +++ b/owslib/swe/common.py @@ -24,7 +24,7 @@ def make_pair(string, cast=None): string = string.split(" ") if cast is not None: try: - string = map(lambda x: cast(x), string) + string = [cast(x) for x in string] except: print("Could not cast pair to correct type. Setting to an empty tuple!") string = "" @@ -60,9 +60,9 @@ def get_float(value): except: return None -AnyScalar = map(lambda x: nspv(x), ["swe20:Boolean", "swe20:Count", "swe20:Quantity", "swe20:Time", "swe20:Category", "swe20:Text"]) -AnyNumerical = map(lambda x: nspv(x), ["swe20:Count", "swe20:Quantity", "swe20:Time"]) -AnyRange = map(lambda x: nspv(x), ["swe20:QuantityRange", "swe20:TimeRange", "swe20:CountRange", "swe20:CategoryRange"]) +AnyScalar = [nspv(x) for x in ["swe20:Boolean", "swe20:Count", "swe20:Quantity", "swe20:Time", "swe20:Category", "swe20:Text"]] +AnyNumerical = [nspv(x) for x in ["swe20:Count", "swe20:Quantity", "swe20:Time"]] +AnyRange = [nspv(x) for x in ["swe20:QuantityRange", "swe20:TimeRange", "swe20:CountRange", "swe20:CategoryRange"]] class NamedObject(object): def __init__(self, element): @@ -111,7 +111,7 @@ class AbstractSimpleComponent(AbstractDataComponent): self.axisID = testXMLAttribute(element,"axisID") # string, optional # Elements - self.quality = filter(None, [Quality(q) for q in [e.find('*') for e in element.findall(nspv("swe20:quality"))] if q is not None]) + self.quality = [_f for _f in [Quality(q) for q in [e.find('*') for e in element.findall(nspv("swe20:quality"))] if q is not None] if _f] try: self.nilValues = NilValues(element.find(nspv("swe20:nilValues"))) except: @@ -134,7 +134,7 @@ class Quality(object): class NilValues(AbstractSWE): def __init__(self, element): super(NilValues, self).__init__(element) - self.nilValue = filter(None, [nilValue(x) for x in element.findall(nspv("swe20:nilValue"))]) # string, min=0, max=X + self.nilValue = [_f for _f in [nilValue(x) for x in element.findall(nspv("swe20:nilValue"))] if _f] # string, min=0, max=X class nilValue(object): def __init__(self, element): @@ -144,21 +144,21 @@ class nilValue(object): class AllowedTokens(AbstractSWE): def __init__(self, element): super(AllowedTokens, self).__init__(element) - self.value = filter(None, [testXMLValue(x) for x in element.findall(nspv("swe20:value"))]) # string, min=0, max=X + self.value = [_f for _f in [testXMLValue(x) for x in element.findall(nspv("swe20:value"))] if _f] # string, min=0, max=X self.pattern = testXMLValue(element.find(nspv("swe20:pattern"))) # string (Unicode Technical Standard #18, Version 13), min=0 class AllowedValues(AbstractSWE): def __init__(self, element): super(AllowedValues, self).__init__(element) - self.value = filter(None, map(lambda x: get_float(x), [testXMLValue(x) for x in element.findall(nspv("swe20:value"))])) - self.interval = filter(None, [make_pair(testXMLValue(x)) for x in element.findall(nspv("swe20:interval"))]) + self.value = [_f for _f in [get_float(x) for x in [testXMLValue(x) for x in element.findall(nspv("swe20:value"))]] if _f] + self.interval = [_f for _f in [make_pair(testXMLValue(x)) for x in element.findall(nspv("swe20:interval"))] if _f] self.significantFigures = get_int(testXMLValue(element.find(nspv("swe20:significantFigures")))) # integer, min=0 class AllowedTimes(AbstractSWE): def __init__(self, element): super(AllowedTimes, self).__init__(element) - self.value = filter(None, [testXMLValue(x) for x in element.findall(nspv("swe20:value"))]) - self.interval = filter(None, [make_pair(testXMLValue(x)) for x in element.findall(nspv("swe20:interval"))]) + self.value = [_f for _f in [testXMLValue(x) for x in element.findall(nspv("swe20:value"))] if _f] + self.interval = [_f for _f in [make_pair(testXMLValue(x)) for x in element.findall(nspv("swe20:interval"))] if _f] self.significantFigures = get_int(testXMLValue(element.find(nspv("swe20:significantFigures")))) # integer, min=0 class Boolean(AbstractSimpleComponent): @@ -390,11 +390,11 @@ class AbstractEncoding(object): def __new__(cls, element): t = element[-1].tag.split("}")[-1] if t == "TextEncoding": - return super(AbstractEncoding, cls).__new__(TextEncoding, element) + return super(AbstractEncoding, cls).__new__(TextEncoding) elif t == "XMLEncoding": - return super(AbstractEncoding, cls).__new__(XMLEncoding, element) + return super(AbstractEncoding, cls).__new__(XMLEncoding) elif t == "BinaryEncoding": - return super(AbstractEncoding, cls).__new__(BinaryEncoding, element) + return super(AbstractEncoding, cls).__new__(BinaryEncoding) class TextEncoding(AbstractEncoding): def __init__(self, element): diff --git a/owslib/swe/observation/sos100.py b/owslib/swe/observation/sos100.py index 06e5b1d..21bbc4e 100644 --- a/owslib/swe/observation/sos100.py +++ b/owslib/swe/observation/sos100.py @@ -3,7 +3,10 @@ from __future__ import (absolute_import, division, print_function) import cgi from owslib.etree import etree from datetime import datetime -from urllib import urlencode +try: # Python 3 + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode from owslib import ows from owslib.crs import Crs from owslib.fes import FilterCapabilities @@ -12,7 +15,7 @@ from owslib.namespaces import Namespaces def get_namespaces(): n = Namespaces() - ns = n.get_namespaces(["ogc","sml","gml","om","sos","swe","xlink"]) + ns = n.get_namespaces(["ogc","sa","sml","gml","sos","swe","xlink"]) ns["ows"] = n.get_namespace("ows110") return ns namespaces = get_namespaces() @@ -145,6 +148,7 @@ class SensorObservationService_1_0_0(object): offerings=None, observedProperties=None, eventTime=None, + procedure=None, method='Get', **kwargs): """ @@ -183,6 +187,9 @@ class SensorObservationService_1_0_0(object): if 'timeout' in kwargs: url_kwargs['timeout'] = kwargs.pop('timeout') # Client specified timeout value + if procedure is not None: + request['procedure'] = procedure + if kwargs: for kw in kwargs: request[kw]=kwargs[kw] @@ -270,6 +277,9 @@ class SosObservationOffering(object): def __str__(self): return 'Offering id: %s, name: %s' % (self.id, self.name) + def __repr__(self): + return "<SosObservationOffering '%s'>" % self.name + class SosCapabilitiesReader(object): def __init__(self, version="1.0.0", url=None, username=None, password=None): self.version = version @@ -316,6 +326,6 @@ class SosCapabilitiesReader(object): st should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, bytes): + raise ValueError("String must be of type bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/swe/observation/sos200.py b/owslib/swe/observation/sos200.py index 5a0d45f..4894908 100644 --- a/owslib/swe/observation/sos200.py +++ b/owslib/swe/observation/sos200.py @@ -3,7 +3,10 @@ from __future__ import (absolute_import, division, print_function) import cgi from owslib.etree import etree from datetime import datetime -from urllib import urlencode +try: # Python 3 + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode from owslib import ows from owslib.crs import Crs from owslib.fes import FilterCapabilities200 @@ -12,7 +15,7 @@ from owslib.namespaces import Namespaces def get_namespaces(): n = Namespaces() - ns = n.get_namespaces(["fes","ogc","om","gml32","sml","swe20","swes","xlink"]) + ns = n.get_namespaces(["fes","ogc","om","gml32","sa","sml","swe20","swes","xlink"]) ns["ows"] = n.get_namespace("ows110") ns["sos"] = n.get_namespace("sos20") return ns @@ -179,6 +182,9 @@ class SensorObservationService_2_0_0(object): if 'timeout' in kwargs: url_kwargs['timeout'] = kwargs.pop('timeout') # Client specified timeout value + if procedure is not None: + request['procedure'] = procedure + if kwargs: for kw in kwargs: request[kw]=kwargs[kw] @@ -264,6 +270,9 @@ class SosObservationOffering(object): def __str__(self): return 'Offering id: %s, name: %s' % (self.id, self.name) + + def __repr__(self): + return "<SosObservationOffering '%s'>" % self.name class SosCapabilitiesReader(object): def __init__(self, version="2.0.0", url=None, username=None, password=None): @@ -311,6 +320,6 @@ class SosCapabilitiesReader(object): st should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, str) and not isinstance(st, bytes): + raise ValueError("String must be of type string or bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/swe/sensor/sml.py b/owslib/swe/sensor/sml.py index 28e199e..61c7ed9 100644 --- a/owslib/swe/sensor/sml.py +++ b/owslib/swe/sensor/sml.py @@ -19,7 +19,7 @@ def nsp(path): class SensorML(object): def __init__(self, element): - if isinstance(element, str): + if isinstance(element, str) or isinstance(element, bytes): self._root = etree.fromstring(element) else: self._root = element diff --git a/owslib/tms.py b/owslib/tms.py index c5eff4b..9bfa59c 100644 --- a/owslib/tms.py +++ b/owslib/tms.py @@ -18,7 +18,7 @@ from __future__ import (absolute_import, division, print_function) from .etree import etree -from .util import openURL, testXMLValue +from .util import openURL, testXMLValue, ServiceException FORCE900913 = False @@ -40,14 +40,13 @@ class TileMapService(object): Implements IWebMapService. """ - def __init__(self, url, version='1.0.0', xml=None, - username=None, password=None, parse_remote_metadata=False - ): + def __init__(self, url, version='1.0.0', xml=None, username=None, password=None, parse_remote_metadata=False, timeout=30): """Initialize.""" self.url = url self.username = username self.password = password self.version = version + self.timeout = timeout self.services = None self._capabilities = None self.contents={} @@ -59,7 +58,7 @@ class TileMapService(object): if xml: # read from stored xml self._capabilities = reader.readString(xml) else: # read from server - self._capabilities = reader.read(self.url) + self._capabilities = reader.read(self.url, timeout=self.timeout) # build metadata objects self._buildMetadata(parse_remote_metadata) @@ -117,22 +116,22 @@ class TileMapService(object): items.append((item,self.contents[item])) return items - def _gettilefromset(self, tilesets, x, y,z, ext): + def _gettilefromset(self, tilesets, x, y,z, ext, timeout=None): for tileset in tilesets: if tileset['order'] == z: url = tileset['href'] + '/' + str(x) +'/' + str(y) + '.' + ext u = openURL(url, '', username = self.username, - password = self.password) + password = self.password, timeout=timeout or self.timeout) return u else: raise ValueError('cannot find zoomlevel %i for TileMap' % z) - def gettile(self, x,y,z, id=None, title=None, srs=None, mimetype=None): + def gettile(self, x,y,z, id=None, title=None, srs=None, mimetype=None, timeout=None): if not id and not title and not srs: raise ValueError('either id or title and srs must be specified') if id: return self._gettilefromset(self.contents[id].tilemap.tilesets, - x, y, z, self.contents[id].tilemap.extension) + x, y, z, self.contents[id].tilemap.extension, timeout=timeout) elif title and srs: for tm in self.contents.values(): @@ -140,12 +139,12 @@ class TileMapService(object): if mimetype: if tm.tilemap.mimetype == mimetype: return self._gettilefromset(tm.tilemap.tilesets, - x, y, z, tm.tilemap.extension) + x, y, z, tm.tilemap.extension, timeout=timeout) else: #if no format is given we return the tile from the # first tilemap that matches name and srs return self._gettilefromset(tm.tilemap.tilesets, - x, y,z, tm.tilemap.extension) + x, y,z, tm.tilemap.extension, timeout=timeout) else: raise ValueError('cannot find %s with projection %s for zoomlevel %i' %(title, srs, z) ) @@ -161,7 +160,7 @@ class ServiceIdentification(object): def __init__(self, infoset, version): self._root=infoset if self._root.tag != 'TileMapService': - raise ServiceException + raise ServiceException("Expected TileMapService tag, got %s" % self._root.tag) self.version = version self.title = testXMLValue(self._root.find('Title')) self.abstract = testXMLValue(self._root.find('Abstract')) @@ -318,11 +317,11 @@ class TMSCapabilitiesReader(object): self.password = pw - def read(self, service_url): + def read(self, service_url, timeout=30): """Get and parse a TMS capabilities document, returning an elementtree instance """ - u = openURL(service_url, '', method='Get', username = self.username, password = self.password) + u = openURL(service_url, '', method='Get', username=self.username, password=self.password, timeout=timeout) return etree.fromstring(u.read()) def readString(self, st): diff --git a/owslib/util.py b/owslib/util.py index cc586c0..a4b023c 100644 --- a/owslib/util.py +++ b/owslib/util.py @@ -9,59 +9,36 @@ from __future__ import (absolute_import, division, print_function) -import base64 import sys from dateutil import parser from datetime import datetime import pytz -from owslib.etree import etree +from owslib.etree import etree, ParseError from owslib.namespaces import Namespaces -import urlparse, urllib2 -from urllib2 import urlopen, HTTPError, Request -from urllib2 import HTTPPasswordMgrWithDefaultRealm -from urllib2 import HTTPBasicAuthHandler -from StringIO import StringIO +try: # Python 3 + from urllib.parse import urlsplit, urlencode +except ImportError: # Python 2 + from urlparse import urlsplit + from urllib import urlencode + +try: + from StringIO import StringIO # Python 2 + BytesIO = StringIO +except ImportError: + from io import StringIO, BytesIO # Python 3 + import cgi -from urllib import urlencode import re from copy import deepcopy import warnings import time - +import six +import requests """ Utility functions and classes """ -class RereadableURL(StringIO,object): - """ Class that acts like a combination of StringIO and url - has seek method and url headers etc """ - def __init__(self, u): - #get url headers etc from url - self.headers = u.headers - #get file like seek, read methods from StringIO - content=u.read() - #Due to race conditions the XML file might be empty. In that case the parsing method would - #throw an exception. This issue can be fixed by requesting the url again and again - #until the content is non-empty. To avoid an endless loop a time limit is hardcoded. - timelimit = 10.0#sleep for an accumulated maximum of 10 seconds before giving up. - timestep = 0.25 - timecur = 0.0 - while content == "": - page = urllib2.urlopen(u.url) - text = page.read() - #The header line with <?xml... should not be in content. - if "<?xml" == text.strip()[:5]: - content = "\n".join(text.split("\n")[1:]) - else: - content = text - if timecur > timelimit: - break#The parsing with se_tree = etree.fromstring(se_xml) will throw an exception. - if content == "": - time.sleep(timestep) - timecur += timestep - super(RereadableURL, self).__init__(content) - - class ServiceException(Exception): #TODO: this should go in ows common module when refactored. pass @@ -69,7 +46,7 @@ class ServiceException(Exception): # http://stackoverflow.com/questions/6256183/combine-two-dictionaries-of-dictionaries-python dict_union = lambda d1,d2: dict((x,(dict_union(d1.get(x,{}),d2[x]) if isinstance(d2.get(x),dict) else d2.get(x,d1.get(x)))) for x in - set(d1.keys()+d2.keys())) + set(list(d1.keys())+list(d2.keys()))) # Infinite DateTimes for Python. Used in SWE 2.0 and other OGC specs as "INF" and "-INF" @@ -140,64 +117,85 @@ def xml_to_dict(root, prefix=None, depth=1, diction=None): return ret -def openURL(url_base, data, method='Get', cookies=None, username=None, password=None, timeout=30): - ''' function to open urls - wrapper around urllib2.urlopen but with additional checks for OGC service exceptions and url formatting, also handles cookies and simple user password authentication''' - url_base.strip() - lastchar = url_base[-1] - if lastchar not in ['?', '&']: - if url_base.find('?') == -1: - url_base = url_base + '?' - else: - url_base = url_base + '&' - +class ResponseWrapper(object): + """ + Return object type from openURL. + + Provides a thin shim around requests response object to maintain code compatibility. + """ + def __init__(self, response): + self._response = response + + def info(self): + return self._response.headers + + def read(self): + if not self._response.encoding: + return self._response.content # bytes + + return self._response.text.encode('utf-8') # str + + def geturl(self): + return self._response.url + + # @TODO: __getattribute__ for poking at response + +def openURL(url_base, data=None, method='Get', cookies=None, username=None, password=None, timeout=30): + """ + Function to open URLs. + + Uses requests library but with additional checks for OGC service exceptions and url formatting. + Also handles cookies and simple user password authentication. + """ + auth = None if username and password: - # Provide login information in order to use the WMS server - # Create an OpenerDirector with support for Basic HTTP - # Authentication... - passman = HTTPPasswordMgrWithDefaultRealm() - passman.add_password(None, url_base, username, password) - auth_handler = HTTPBasicAuthHandler(passman) - opener = urllib2.build_opener(auth_handler) - openit = opener.open + auth = (username, password) + + headers = {} + rkwargs = {'timeout':timeout} + + # FIXUP for WFS in particular, remove xml style namespace + # @TODO does this belong here? + method = method.split("}")[-1] + + if method.lower() == 'post': + try: + xml = etree.fromstring(data) + headers['Content-Type'] = "text/xml" + except (ParseError, UnicodeEncodeError): + pass + + rkwargs['data'] = data + + elif method.lower() == 'get': + rkwargs['params'] = data else: - # NOTE: optionally set debuglevel>0 to debug HTTP connection - #opener = urllib2.build_opener(urllib2.HTTPHandler(debuglevel=0)) - #openit = opener.open - openit = urlopen - - try: - if method == 'Post': - req = Request(url_base, data) - # set appropriate header if posting XML - try: - xml = etree.fromstring(data) - req.add_header('Content-Type', "text/xml") - except: - pass - else: - req=Request(url_base + data) - if cookies is not None: - req.add_header('Cookie', cookies) - u = openit(req, timeout=timeout) - except HTTPError as e: #Some servers may set the http header to 400 if returning an OGC service exception or 401 if unauthorised. - if e.code in [400, 401]: - raise ServiceException(e.read()) - else: - raise e + raise ValueError("Unknown method ('%s'), expected 'get' or 'post'" % method) + + if cookies is not None: + rkwargs['cookies'] = cookies + + req = requests.request(method.upper(), + url_base, + **rkwargs) + + if req.status_code in [400, 401]: + raise ServiceException(req.text) + + if req.status_code in [404]: # add more if needed + req.raise_for_status() + # check for service exceptions without the http header set - if 'Content-Type' in u.info() and u.info()['Content-Type'] in ['text/xml', 'application/xml']: + if 'Content-Type' in req.headers and req.headers['Content-Type'] in ['text/xml', 'application/xml']: #just in case 400 headers were not set, going to have to read the xml to see if it's an exception report. - #wrap the url stram in a extended StringIO object so it's re-readable - u=RereadableURL(u) - se_xml= u.read() - se_tree = etree.fromstring(se_xml) + se_tree = etree.fromstring(req.content) serviceException=se_tree.find('{http://www.opengis.net/ows}Exception') if serviceException is None: serviceException=se_tree.find('ServiceException') if serviceException is not None: raise ServiceException(str(serviceException.text).strip()) - u.seek(0) #return cursor to start of u - return u + + return ResponseWrapper(req) #default namespace for nspath is OWS common OWS_NAMESPACE = 'http://www.opengis.net/ows/1.1' @@ -243,12 +241,12 @@ def cleanup_namespaces(element): def add_namespaces(root, ns_keys): - if isinstance(ns_keys, basestring): + if isinstance(ns_keys, six.string_types): ns_keys = [ns_keys] namespaces = Namespaces() - ns_keys = map(lambda x: (x, namespaces.get_namespace(x)), ns_keys) + ns_keys = [(x, namespaces.get_namespace(x)) for x in ns_keys] if etree.__name__ != 'lxml.etree': # We can just add more namespaces when not using lxml. @@ -273,7 +271,7 @@ def add_namespaces(root, ns_keys): # Recreate the root element with updated nsmap new_root = etree.Element(root.tag, nsmap=new_map) # Carry over attributes - for a, v in root.items(): + for a, v in list(root.items()): new_root.set(a, v) # Carry over children for child in root: @@ -355,41 +353,31 @@ def http_post(url=None, request=None, lang='en-US', timeout=10, username=None, p """ - if url is not None: - u = urlparse.urlsplit(url) - r = urllib2.Request(url, request) - r.add_header('User-Agent', 'OWSLib (https://geopython.github.io/OWSLib)') - r.add_header('Content-type', 'text/xml') - r.add_header('Content-length', '%d' % len(request)) - r.add_header('Accept', 'text/xml') - r.add_header('Accept-Language', lang) - r.add_header('Accept-Encoding', 'gzip,deflate') - r.add_header('Host', u.netloc) - - if username is not None and password is not None: - base64string = base64.encodestring('%s:%s' % (username, password))[:-1] - r.add_header('Authorization', 'Basic %s' % base64string) - try: - up = urllib2.urlopen(r,timeout=timeout); - except TypeError: - import socket - socket.setdefaulttimeout(timeout) - up = urllib2.urlopen(r) + if url is None: + raise ValueError("URL required") + + u = urlsplit(url) + + headers = { + 'User-Agent' : 'OWSLib (https://geopython.github.io/OWSLib)', + 'Content-type' : 'text/xml', + 'Content-length' : '%d' % len(request), + 'Accept' : 'text/xml', + 'Accept-Language' : lang, + 'Accept-Encoding' : 'gzip,deflate', + 'Host' : u.netloc, + } - ui = up.info() # headers - response = up.read() - up.close() + rkwargs = {} - # check if response is gzip compressed - if 'Content-Encoding' in ui: - if ui['Content-Encoding'] == 'gzip': # uncompress response - import gzip - cds = StringIO(response) - gz = gzip.GzipFile(fileobj=cds) - response = gz.read() + if username is not None and password is not None: + rkwargs['auth'] = (username, password) - return response + up = requests.post(url, request, headers=headers, **rkwargs) + if not up.encoding: + return up.content # bytes + return up.text.encode('utf-8') # str def element_to_string(element, encoding=None, xml_declaration=False): """ @@ -484,7 +472,7 @@ def build_get_url(base_url, params): pars = [x[0] for x in qs] - for key,value in params.iteritems(): + for key,value in six.iteritems(params): if key not in pars: qs.append( (key,value) ) @@ -494,7 +482,7 @@ def build_get_url(base_url, params): def dump(obj, prefix=''): '''Utility function to print to standard output a generic object with all its attributes.''' - print("%s %s : %s" % (prefix, obj.__class__, obj.__dict__)) + print("%s %s.%s : %s" % (prefix, obj.__module__, obj.__class__.__name__, obj.__dict__)) def getTypedValue(type, value): ''' Utility function to cast a string value to the appropriate XSD type. ''' @@ -546,7 +534,7 @@ a newline. This will extract out all of the keywords correctly. """ keywords = [re.split(r'[\n\r]+',f.text) for f in elements if f.text] flattened = [item.strip() for sublist in keywords for item in sublist] - remove_blank = filter(None, flattened) + remove_blank = [_f for _f in flattened if _f] return remove_blank diff --git a/owslib/waterml/wml10.py b/owslib/waterml/wml10.py index f90dfe4..0f0c0f5 100644 --- a/owslib/waterml/wml10.py +++ b/owslib/waterml/wml10.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) from owslib.waterml.wml import SitesResponse, TimeSeriesResponse, VariablesResponse, namespaces -from owslib.etree import etree +from owslib.etree import etree, ElementType def ns(namespace): return namespaces.get(namespace) @@ -9,10 +9,10 @@ def ns(namespace): class WaterML_1_0(object): def __init__(self, element): - if isinstance(element, str) or isinstance(element, unicode): - self._root = etree.fromstring(str(element)) - else: + if isinstance(element, ElementType): self._root = element + else: + self._root = etree.fromstring(element) if hasattr(self._root, 'getroot'): self._root = self._root.getroot() diff --git a/owslib/waterml/wml11.py b/owslib/waterml/wml11.py index f95a27b..ac8ef47 100644 --- a/owslib/waterml/wml11.py +++ b/owslib/waterml/wml11.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) from owslib.waterml.wml import SitesResponse, TimeSeriesResponse, VariablesResponse, namespaces -from owslib.etree import etree +from owslib.etree import etree, ElementType def ns(namespace): return namespaces.get(namespace) @@ -9,10 +9,10 @@ def ns(namespace): class WaterML_1_1(object): def __init__(self, element): - if isinstance(element, str) or isinstance(element, unicode): - self._root = etree.fromstring(str(element)) - else: + if isinstance(element, ElementType): self._root = element + else: + self._root = etree.fromstring(element) if hasattr(self._root, 'getroot'): self._root = self._root.getroot() diff --git a/owslib/wcs.py b/owslib/wcs.py index 572c87a..b67e771 100644 --- a/owslib/wcs.py +++ b/owslib/wcs.py @@ -15,29 +15,27 @@ Web Coverage Server (WCS) methods and metadata. Factory function. from __future__ import (absolute_import, division, print_function) -import urllib2 from . import etree -from .coverage import wcs100, wcs110, wcsBase +from .coverage import wcs100, wcs110, wcs111, wcsBase +from owslib.util import openURL def WebCoverageService(url, version=None, xml=None, cookies=None, timeout=30): ''' wcs factory function, returns a version specific WebCoverageService object ''' - + if version is None: if xml is None: reader = wcsBase.WCSCapabilitiesReader() request = reader.capabilities_url(url) - if cookies is None: - xml = urllib2.urlopen(request, timeout=timeout).read() - else: - req = urllib2.Request(request) - req.add_header('Cookie', cookies) - xml=urllib2.urlopen(req, timeout=timeout) + xml = openURL(request, cookies=cookies, timeout=timeout).read() + capabilities = etree.etree.fromstring(xml) version = capabilities.get('version') del capabilities - + if version == '1.0.0': return wcs100.WebCoverageService_1_0_0.__new__(wcs100.WebCoverageService_1_0_0, url, xml, cookies) elif version == '1.1.0': return wcs110.WebCoverageService_1_1_0.__new__(wcs110.WebCoverageService_1_1_0,url, xml, cookies) + elif version == '1.1.1': + return wcs111.WebCoverageService_1_1_1.__new__(wcs111.WebCoverageService_1_1_1,url, xml, cookies) diff --git a/owslib/wms.py b/owslib/wms.py index bfe7597..238635b 100644 --- a/owslib/wms.py +++ b/owslib/wms.py @@ -18,9 +18,15 @@ Currently supports only version 1.1.1 of the WMS protocol. from __future__ import (absolute_import, division, print_function) import cgi -import urllib2 -from urllib import urlencode +try: # Python 3 + from urllib.parse import urlencode +except ImportError: # Python 2 + from urllib import urlencode + import warnings + +import six + from .etree import etree from .util import openURL, testXMLValue, extract_xml_list, xmltag_split from .fgdc import Metadata @@ -55,30 +61,27 @@ class WebMapService(object): def __getitem__(self,name): ''' check contents dictionary to allow dict like access to service layers''' - if name in self.__getattribute__('contents').keys(): + if name in self.__getattribute__('contents'): return self.__getattribute__('contents')[name] else: raise KeyError("No content named %s" % name) - def __init__(self, url, version='1.1.1', xml=None, - username=None, password=None, parse_remote_metadata=False - ): + def __init__(self, url, version='1.1.1', xml=None, username=None, password=None, parse_remote_metadata=False, timeout=30): """Initialize.""" self.url = url self.username = username self.password = password self.version = version + self.timeout = timeout self._capabilities = None - + # Authentication handled by Reader - reader = WMSCapabilitiesReader( - self.version, url=self.url, un=self.username, pw=self.password - ) + reader = WMSCapabilitiesReader(self.version, url=self.url, un=self.username, pw=self.password) if xml: # read from stored xml self._capabilities = reader.readString(xml) else: # read from server - self._capabilities = reader.read(self.url) + self._capabilities = reader.read(self.url, timeout=self.timeout) # avoid building capabilities metadata if the response is a ServiceExceptionReport se = self._capabilities.find('ServiceException') @@ -150,7 +153,7 @@ class WebMapService(object): ) u = self._open(reader.capabilities_url(self.url)) # check for service exceptions, and return - if u.info().gettype() == 'application/vnd.ogc.se_xml': + if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml': se_xml = u.read() se_tree = etree.fromstring(se_xml) err_message = str(se_tree.find('ServiceException').text).strip() @@ -162,6 +165,7 @@ class WebMapService(object): bgcolor='#FFFFFF', exceptions='application/vnd.ogc.se_xml', method='Get', + timeout=None, **kwargs ): """Request and return an image from the WMS as a file-like object. @@ -200,8 +204,8 @@ class WebMapService(object): size=(300, 300),\ format='image/jpeg',\ transparent=True) - >>> out = open('example.jpg.jpg', 'wb') - >>> out.write(img.read()) + >>> out = open('example.jpg', 'wb') + >>> bytes_written = out.write(img.read()) >>> out.close() """ @@ -240,13 +244,13 @@ class WebMapService(object): data = urlencode(request) - u = openURL(base_url, data, method, username = self.username, password = self.password) + u = openURL(base_url, data, method, username=self.username, password=self.password, timeout=timeout or self.timeout) # check for service exceptions, and return if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml': se_xml = u.read() se_tree = etree.fromstring(se_xml) - err_message = unicode(se_tree.find('ServiceException').text).strip() + err_message = six.text_type(se_tree.find('ServiceException').text).strip() raise ServiceException(err_message, se_xml) return u @@ -411,7 +415,7 @@ class ContentMetadata: ## some servers found in the wild use a single SRS ## tag containing a whitespace separated list of SRIDs ## instead of several SRS tags. hence the inner loop - for srslist in map(lambda x: x.text, elem.findall('SRS')): + for srslist in [x.text for x in elem.findall('SRS')]: if srslist: for srs in srslist.split(): self.crsOptions.append(srs) @@ -481,7 +485,7 @@ class ContentMetadata: if metadataUrl['url'] is not None and parse_remote_metadata: # download URL try: - content = urllib2.urlopen(metadataUrl['url'], timeout=timeout) + content = openURL(metadataUrl['url'], timeout=timeout) doc = etree.parse(content) if metadataUrl['type'] is not None: if metadataUrl['type'] == 'FGDC': @@ -610,7 +614,7 @@ class WMSCapabilitiesReader: urlqs = urlencode(tuple(qs)) return service_url.split('?')[0] + '?' + urlqs - def read(self, service_url): + def read(self, service_url, timeout=30): """Get and parse a WMS capabilities document, returning an elementtree instance @@ -621,7 +625,7 @@ class WMSCapabilitiesReader: #now split it up again to use the generic openURL function... spliturl=getcaprequest.split('?') - u = openURL(spliturl[0], spliturl[1], method='Get', username = self.username, password = self.password) + u = openURL(spliturl[0], spliturl[1], method='Get', username=self.username, password=self.password, timeout=timeout) return etree.fromstring(u.read()) def readString(self, st): @@ -629,6 +633,6 @@ class WMSCapabilitiesReader: string should be an XML capabilities document """ - if not isinstance(st, str): - raise ValueError("String must be of type string, not %s" % type(st)) + if not isinstance(st, str) and not isinstance(st, bytes): + raise ValueError("String must be of type string or bytes, not %s" % type(st)) return etree.fromstring(st) diff --git a/owslib/wmts.py b/owslib/wmts.py index 9b4d78d..f915f6f 100644 --- a/owslib/wmts.py +++ b/owslib/wmts.py @@ -32,9 +32,14 @@ would be appreciated. from __future__ import (absolute_import, division, print_function) import warnings -import urlparse -import urllib2 -from urllib import urlencode +import six +from six.moves import filter +try: # Python 3 + from urllib.parse import (urlencode, urlparse, urlunparse, parse_qs, + ParseResult) +except ImportError: # Python 2 + from urllib import urlencode + from urlparse import urlparse, urlunparse, parse_qs, ParseResult from .etree import etree from .util import openURL, testXMLValue, getXMLInteger from .fgdc import Metadata @@ -115,7 +120,7 @@ class WebMapTileService(object): def __getitem__(self, name): '''Check contents dictionary to allow dict like access to service layers''' - if name in self.__getattribute__('contents').keys(): + if name in self.__getattribute__('contents'): return self.__getattribute__('contents')[name] else: raise KeyError("No content named %s" % name) @@ -293,7 +298,7 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' if (layer is None): raise ValueError("layer is mandatory (cannot be None)") if style is None: - style = self[layer].styles.keys()[0] + style = list(self[layer].styles.keys())[0] if format is None: format = self[layer].formats[0] if tilematrixset is None: @@ -318,7 +323,7 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' request.append(('TILECOL', str(column))) request.append(('FORMAT', format)) - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): request.append((key, value)) data = urlencode(request, True) @@ -368,7 +373,7 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' tilematrix='6',\ row=4, column=4) >>> out = open('tile.jpg', 'wb') - >>> out.write(img.read()) + >>> bytes_written = out.write(img.read()) >>> out.close() """ @@ -380,9 +385,9 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' if base_url is None: base_url = self.url try: - get_verbs = filter( - lambda x: x.get('type').lower() == 'get', - self.getOperationByName('GetTile').methods) + methods = self.getOperationByName('GetTile').methods + get_verbs = [x for x in methods + if x.get('type').lower() == 'get'] if len(get_verbs) > 1: # Filter by constraints base_url = next( @@ -390,8 +395,7 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' list, ([pv.get('url') for const in pv.get('constraints') - if 'kvp' in map( - lambda x: x.lower(), const.values)] + if 'kvp' in [x.lower() for x in const.values]] for pv in get_verbs if pv.get('constraints'))))[0] elif len(get_verbs) == 1: base_url = get_verbs[0].get('url') @@ -404,7 +408,7 @@ TILEMATRIX=6&TILEROW=4&TILECOL=4&FORMAT=image%2Fjpeg' if u.info()['Content-Type'] == 'application/vnd.ogc.se_xml': se_xml = u.read() se_tree = etree.fromstring(se_xml) - err_message = unicode(se_tree.find('ServiceException').text) + err_message = six.text_type(se_tree.find('ServiceException').text) raise ServiceException(err_message.strip(), se_xml) return u @@ -703,8 +707,8 @@ class WMTSCapabilitiesReader: """ # Ensure the 'service', 'request', and 'version' parameters, # and any vendor-specific parameters are included in the URL. - pieces = urlparse.urlparse(service_url) - args = urlparse.parse_qs(pieces.query) + pieces = urlparse(service_url) + args = parse_qs(pieces.query) if 'service' not in args: args['service'] = 'WMTS' if 'request' not in args: @@ -714,10 +718,10 @@ class WMTSCapabilitiesReader: if vendor_kwargs: args.update(vendor_kwargs) query = urlencode(args, doseq=True) - pieces = urlparse.ParseResult(pieces.scheme, pieces.netloc, - pieces.path, pieces.params, - query, pieces.fragment) - return urlparse.urlunparse(pieces) + pieces = ParseResult(pieces.scheme, pieces.netloc, + pieces.path, pieces.params, + query, pieces.fragment) + return urlunparse(pieces) def read(self, service_url, vendor_kwargs=None): """Get and parse a WMTS capabilities document, returning an @@ -740,7 +744,7 @@ class WMTSCapabilitiesReader: string should be an XML capabilities document """ - if not isinstance(st, str): - msg = 'String must be of type string, not %s' % type(st) + if not isinstance(st, str) and not isinstance(st, bytes): + msg = 'String must be of type string or bytes, not %s' % type(st) raise ValueError(msg) return etree.fromstring(st) diff --git a/owslib/wps.py b/owslib/wps.py index 0463407..43de1b0 100644 --- a/owslib/wps.py +++ b/owslib/wps.py @@ -377,7 +377,7 @@ class WPSReader(object): Method to read a WPS GetCapabilities document from an XML string. """ - if not isinstance(string, str): + if not isinstance(string, str) and not isinstance(string, bytes): raise ValueError("Input must be of type string, not %s" % type(string)) return etree.fromstring(string) diff --git a/requirements.txt b/requirements.txt index 8b6b3b8..508d303 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ python-dateutil>=1.5 pytz +requests>=2.7 diff --git a/setup.py b/setup.py index 7ba54c1..a316d64 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from setuptools import setup, find_packages import owslib from setuptools.command.test import test as TestCommand -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/owslib.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel