[owslib] 01/04: Imported Upstream version 0.9.0

Johan Van de Wauw johanvdw-guest at moszumanska.debian.org
Tue Jun 16 19:00:53 UTC 2015


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.vandewauw at 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.sousa at gmail.com>
+#
+# Contact email: luis.a.de.sousa at 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



More information about the Pkg-grass-devel mailing list