diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 6c1f109aa0506d68a5113e02a0f40154f3e8e2c2..7031d5408deab3f0a7feca6051cdb226e1101732 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2013 Neogeo Technologies.
+Copyright (C) 2011-2020 Neogeo Technologies.
 All Rights Reserved.
 
 MapServer Rest API is free software: you can redistribute it 
diff --git a/README.md b/README.md
index 7b24c6ce6967ee5203a7f7bd9a78fb77c03be525..aa370e2dbe4c4de858d4d0dc4f7b98262a8a26b3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # MapServer Rest API
 
 **MapServer REST API** is a python wrapper around MapServer which allows to
-manipulate a mapfile in a RESTFul way. It has been developped to match as 
+manipulate a mapfile in a RESTFul way. It has been developped to match as
 close as possible the way the GeoServer REST API acts.
 
 ## Installation
@@ -10,8 +10,8 @@ See the Mapserver Rest API Documentation for installation instructions.
 
 ## Copying and license
 
-**MapServer REST API** is copyright (c) 2011-2013 Neogeo Technologies.
+**MapServer REST API** is Copyright (C) 2011-2020 Neogeo Technologies.
 
 It is free software licensed under the GNU General Public License version 3.
 You should have received a copy of the GNU General Public License along with
-this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
\ No newline at end of file
+this program. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
diff --git a/plugins/__init__.py b/plugins/__init__.py
index 347ed97107aa6f0e5001a30f3e1b70d7f8e9ddf8..b32a3a31f87ff20e20bbe24801658d09cd4f5ab2 100644
--- a/plugins/__init__.py
+++ b/plugins/__init__.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -35,4 +32,3 @@ for module in os.listdir(os.path.dirname(__file__)):
     name, ext = os.path.splitext(module)
     if name != "__init__" and ext in ["", ".py"]:
         __all__.append(name)
-
diff --git a/setup.py b/setup.py
index b11a9bbe4e17a788598727156521215c9595b761..964d2eee414dd33407fa1cd824baa4cf4baa3fe6 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -29,11 +26,11 @@ from distutils.core import setup
 
 setup(
     name='MapServer Rest API',
-    version='0.1.0',
+    version='1.0.0',
     author='Neogeo Technologies',
     author_email='contact@neogeo-online.net',
     description='A RESTFul interface for MapServer',
-    long_description=file('README.md','rb').read(),
+    long_description=open('README.md', 'r').read(),
     keywords='neogeo mapserver rest restful',
     license="GPLv3",
     #url='',
@@ -44,16 +41,17 @@ setup(
         'License :: OSI Approved :: GPLv3',
         'Operating System :: OS Independent',
         'Programming Language :: Python',
+        'Programming Language :: Python3',
         'Natural Language :: English',
         'Topic :: Scientific/Engineering :: GIS',
         ],
     #packages=,
     #package_dir={'':'src'},
     #namespace_packages=['mra'],
-    requires=[
-        'web.py',
+    install_requires=[
+        'web.py==0.40',
         'pyyaml',
-        'osgeo',
+        'gdal<2.5.0',
         ],
     scripts=[
         'src/server.py',
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..27b06b41f8163aea594658c73b1cc1286ed07a50
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,22 @@
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+#                                                                       #
+#   MapServer REST API is a python wrapper around MapServer which       #
+#   allows to manipulate a mapfile in a RESTFul way. It has been        #
+#   developped to match as close as possible the way the GeoServer      #
+#   REST API acts.                                                      #
+#                                                                       #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
+#                                                                       #
+#   This file is part of MapServer Rest API.                            #
+#                                                                       #
+#   MapServer Rest API is free software: you can redistribute it        #
+#   and/or modify it under the terms of the GNU General Public License  #
+#   as published by the Free Software Foundation, either version 3 of   #
+#   the License, or (at your option) any later version.                 #
+#                                                                       #
+#   MapServer Rest API is distributed in the hope that it will be       #
+#   useful, but WITHOUT ANY WARRANTY; without even the implied warranty #
+#   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the     #
+#   GNU General Public License for more details.                        #
+#                                                                       #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
diff --git a/src/extensions.py b/src/extensions.py
index d2218cf744304ac94c992bd07bdfb954a6142f17..73bfd49578b08cac104e6f05ce15597e24c976b4 100644
--- a/src/extensions.py
+++ b/src/extensions.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -47,7 +44,7 @@ class ExtensionManager(object):
     def load_plugins_dir(self, dir_path):
         path, pkg = os.path.split(os.path.abspath(dir_path))
         pkg, _ = os.path.splitext(pkg)
-        print "Loading %s from %s" % (pkg, path)
+        print("Loading %s from %s" % (pkg, path))
 
         sys.path.append(path)
         try:
diff --git a/src/metadata.py b/src/metadata.py
index 24165552ecc95188d15e7368f4d5f63430773aae..807eb19bcf42d2f191182c730a693c8b5e24376f 100644
--- a/src/metadata.py
+++ b/src/metadata.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -35,6 +32,7 @@
 import yaml
 import contextlib
 from mapscript import MapServerError
+import logging
 
 
 METADATA_NAME="mra"
@@ -51,7 +49,8 @@ def get_metadata(obj, key, *args):
     try:
         value = obj.getMetaData(key)
     # We never now what mapscript might throws at us...
-    except MapServerError:
+    except MapServerError as e:
+        logging.warning("metadata.py::get_metadata MapServerError: %s", str(e))
         value = None
 
     if value is None:
@@ -84,18 +83,18 @@ def get_metadata_keys(obj):
 def set_metadata(obj, key, value):
     try:
         obj.setMetaData(key, value)
-    except UnicodeEncodeError, e:
+    except UnicodeEncodeError as e:
         obj.setMetaData(key, value.encode('utf8'))
 
 
 def set_metadatas(obj, metadatas):
     # TODO: erease all metadata first.
-    for key, value in metadatas.iteritems():
+    for key, value in metadatas.items():
         set_metadata(obj, key, value)
 
 
 def update_metadatas(obj, metadatas):
-    for key, value in metadatas.iteritems():
+    for key, value in metadatas.items():
         set_metadata(obj, key, value)
 
 
@@ -140,18 +139,19 @@ def get_mra_metadata(obj, key, *args):
 
     try:
         return mra_metadata[key]
-    except KeyError:
+    except KeyError as e:
+        logging.warning("metadata.py::get_mra_metadata KeyError %s", str(e))
         if not args:
             raise
         return args[0]
 
 
 def iter_mra_metadata_keys(obj):
-    return __get_mra(obj).iterkeys()
+    return iter(__get_mra(obj).keys())
 
 
 def get_mra_metadata_keys(obj):
-    return __get_mra(obj).keys()
+    return list(__get_mra(obj).keys())
 
 
 def update_mra_metadatas(obj, update):
diff --git a/src/mra.py b/src/mra.py
index bf87e1a67d283a8802018df4418ed5089b3ec750..039af6cfd061089894c1c4dfb641055d4f524f92 100644
--- a/src/mra.py
+++ b/src/mra.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -39,7 +36,7 @@
 import os
 import os.path
 import string
-import urlparse
+import urllib.parse
 import functools
 import web
 import yaml
@@ -51,6 +48,10 @@ from webapp import KeyExists
 import stores
 import metadata
 import xml.etree.ElementTree as ET
+import logging
+
+
+yaml.warnings({'YAMLLoadWarning': False})
 
 
 # Defines commons outputformats:
@@ -70,7 +71,7 @@ def outputformat(
     if transparent:
         _format.transparent = transparent
     if options:
-        for k, v in options.items():
+        for k, v in list(options.items()):
             _format.setOption(k, v)
     return _format
 
@@ -196,19 +197,19 @@ class Layer(MetadataMixin):
         return iter(self.get_fields())
 
     def iter_classes(self):
-        for i in reversed(xrange(self.ms.numclasses)):
+        for i in reversed(range(self.ms.numclasses)):
             c = Clazz(self.ms.getClass(i))
             c.index = i
             yield c
 
     def get_styles(self):
-        return set(self.ms.getClass(i).group for i in reversed(xrange(self.ms.numclasses)))
+        return set(self.ms.getClass(i).group for i in reversed(range(self.ms.numclasses)))
 
     def iter_styles(self):
         return iter(self.get_styles())
 
     def get_SLD(self):
-        return self.ms.generateSLD().decode("LATIN1").encode("UTF8")
+        return self.ms.generateSLD().decode()
 
     def add_style_sld(self, mf, s_name, new_sld):
         # Because we do not want to apply the sld to real layers by mistake
@@ -224,7 +225,8 @@ class Layer(MetadataMixin):
         try:
             xmlsld.firstChild.getElementsByTagNameNS("*", "NamedLayer")[0]\
                 .getElementsByTagNameNS("*", "Name")[0].firstChild.data = sld_layer_name
-        except:
+        except Exception as e:
+            logging.error("mra.py::Layer::add_style_sld: Bad sld (No NamedLayer/Name): %s", e)
             raise ValueError("Bad sld (No NamedLayer/Name)")
 
         # Remove encoding ?
@@ -237,11 +239,12 @@ class Layer(MetadataMixin):
         mf.ms.insertLayer(ms_template_layer)
 
         try:
-            ms_template_layer.applySLD(new_sld.encode("utf-8"), sld_layer_name)
-        except:
+            ms_template_layer.applySLD(new_sld, sld_layer_name)
+        except Exception as e:
+            logging.error("mra.py::Layer::add_style_sld: Unable to access storage : %s", e)
             raise ValueError("Unable to access storage.")
 
-        for i in xrange(ms_template_layer.numclasses):
+        for i in range(ms_template_layer.numclasses):
             ms_class = ms_template_layer.getClass(i)
             ms_class.group = s_name
             self.ms.insertClass(ms_class)
@@ -263,8 +266,9 @@ class Layer(MetadataMixin):
             return
 
         try:
-            style = open(os.path.join(os.path.dirname(__file__), "%s.sld" % s_name)).read()
-        except IOError, OSError:
+            style = open(os.path.join(os.path.dirname(__file__), "%s.sld" % s_name), encoding="utf-8").read()
+        except IOError as OSError:
+            logging.warning("mra.py::Layer::set_default_style IOError %s", OSError)
             return
 
         self.add_style_sld(mf, s_name, style)
@@ -294,13 +298,13 @@ class LayerGroup(object):
     def add_layer(self, layer):
         layer.ms.group = self.name
         layer.set_metadata("wms_group_name", self.name)
-        for k, v in self.mapfile.get_mra_metadata("layergroups")[self.name].iteritems():
+        for k, v in self.mapfile.get_mra_metadata("layergroups")[self.name].items():
             layer.set_metadata("wms_group_%s" % k, v)
         self.mapfile.move_layer_down(layer.ms.name)
 
     def add(self, *args):
         for layer in args:
-            if isinstance(layer, basestring):
+            if isinstance(layer, str):
                 layer = self.mapfile.get_layer(layer)
             self.add_layer(layer)
 
@@ -313,7 +317,7 @@ class LayerGroup(object):
 
     def remove(self, *args):
         for layer in args:
-            if isinstance(layer, basestring):
+            if isinstance(layer, str):
                 layer = mapfile.get_layer(layer)
             self.remove_layer(layer)
 
@@ -361,17 +365,17 @@ class Mapfile(MetadataMixin):
             self.ms.setSize(1, 1)
 
             for outputformat in [
-                    v for k in OUTPUTFORMAT.keys() for v in list(OUTPUTFORMAT[k].values())]:
+                    v for k in list(OUTPUTFORMAT.keys()) for v in list(OUTPUTFORMAT[k].values())]:
                 self.ms.appendOutputFormat(outputformat)
 
-            for k, v in config.get('metadata', {}).iteritems():
+            for k, v in config.get('metadata', {}).items():
                 self.set_metadata(k, v)
 
             for ows in ("ows", "wms", "wfs", "wcs"):
                 self.set_metadata("%s_enable_request" % ows, "*")
 
             if 'onlineresource' in config:
-                onlineresource = urlparse.urljoin(config.get('onlineresource'), self.ms.name)
+                onlineresource = urllib.parse.urljoin(config.get('onlineresource'), self.ms.name)
                 self.set_metadata('ows_onlineresource', onlineresource)
 
             fontset and self.ms.setFontSet(fontset)
@@ -390,13 +394,13 @@ class Mapfile(MetadataMixin):
         def check(f, v):
             return f(v) if callable(f) else f == v
 
-        for l in xrange(self.ms.numlayers):
+        for l in range(self.ms.numlayers):
             ms_layer = self.ms.getLayer(l)
-            if not all(check(checker, getattr(ms_layer, k, None)) for k, checker in attr.iteritems()):
+            if not all(check(checker, getattr(ms_layer, k, None)) for k, checker in attr.items()):
                 continue
-            if not all(check(checker, metadata.get_metadata(ms_layer, k, None)) for k, checker in meta.iteritems()):
+            if not all(check(checker, metadata.get_metadata(ms_layer, k, None)) for k, checker in meta.items()):
                 continue
-            if not all(check(checker, metadata.get_mra_metadata(ms_layer, k, None)) for k, checker in mra.iteritems()):
+            if not all(check(checker, metadata.get_mra_metadata(ms_layer, k, None)) for k, checker in mra.items()):
                 continue
             yield ms_layer
 
@@ -407,7 +411,7 @@ class Mapfile(MetadataMixin):
     def get_layer(self, l_name):
         try:
             return next(self.iter_layers(attr={"name": l_name}))
-        except StopIteration:
+        except (StopIteration, SystemError):
             raise KeyError(l_name)
 
     def has_layer(self, l_name):
@@ -464,7 +468,7 @@ class Mapfile(MetadataMixin):
         return LayerGroup(lg_name, self)
 
     def iter_layergroups(self):
-        return (LayerGroup(name, self) for name in self.get_mra_metadata("layergroups", {}).iterkeys())
+        return (LayerGroup(name, self) for name in self.get_mra_metadata("layergroups", {}).keys())
 
     def get_layergroup(self, lg_name):
         if lg_name in self.get_mra_metadata("layergroups", {}):
@@ -542,7 +546,7 @@ class FeatureTypeModel(LayerModel):
         # TODO: clean up this fallback.
         else:
             self.ms.connectiontype = mapscript.MS_SHAPEFILE
-            url = urlparse.urlparse(cparam["url"])
+            url = urllib.parse.urlparse(cparam["url"])
             self.ms.data = self.ws.mra.get_file_path(url.path)
 
         # Update mra metadata, and make sure the mandatory ones are left untouched.
@@ -602,7 +606,7 @@ class FeatureTypeModel(LayerModel):
             "gml_%s_type" % geometry_column: ft.get_geomtype_gml(),
             # TODO: Add gml_<geometry name>_occurances,
             "wfs_srs": ws.get_metadata("ows_srs"),
-            "wfs_getfeature_formatlist": ",".join(OUTPUTFORMAT["WFS"].keys())
+            "wfs_getfeature_formatlist": ",".join(list(OUTPUTFORMAT["WFS"].keys()))
             })
 
         if ft.get_fid_column() is not None:
@@ -639,7 +643,7 @@ class CoverageModel(LayerModel):
 
         #if cparam["dbtype"] in ["tif", "tiff"]:
         self.ms.connectiontype = mapscript.MS_RASTER
-        url = urlparse.urlparse(cparam["url"])
+        url = urllib.parse.urlparse(cparam["url"])
         self.ms.data = self.ws.mra.get_file_path(url.path)
             # TODO: strip extention.
         #else:
@@ -731,10 +735,10 @@ class Workspace(Mapfile):
         return info
 
     def iter_store_names(self, st_type):
-        return self.get_mra_metadata("%ss" % st_type, {}).iterkeys()
+        return iter(self.get_mra_metadata("%ss" % st_type, {}).keys())
 
     def iter_stores(self, st_type):
-        return self.get_mra_metadata("%ss" % st_type, {}).iteritems()
+        return iter(self.get_mra_metadata("%ss" % st_type, {}).items())
 
     def create_store(self, st_type, name, configuration):
         with self.mra_metadata("%ss" % st_type, {}) as stores:
@@ -791,7 +795,7 @@ class Workspace(Mapfile):
 
         try:
             next(self.iter_featuretypemodels(name))
-        except StopIteration:
+        except (StopIteration, SystemError):
             pass  # No layers use our store, all OK.
         else:
             raise ValueError("The datastore \"%s\" can't be delete because it is used." % name)
@@ -834,7 +838,7 @@ class Workspace(Mapfile):
 
         try:
             next(self.iter_coveragemodels(name))
-        except StopIteration:
+        except (StopIteration, SystemError):
             pass  # No layers use our store, all OK.
         else:
             raise ValueError("The coveragestore \"%s\" can't be delete because it is used." % name)
@@ -870,7 +874,7 @@ class Workspace(Mapfile):
     def get_layermodel(self, st_type, store, name):
         try:
             return next(self.iter_layermodels(attr={"name": self.__model_name(st_type, store, name)}))
-        except StopIteration:
+        except (StopIteration, SystemError):
             raise KeyError((st_type, store, name))
 
     def has_layermodel(self, st_type, name, store):
@@ -946,7 +950,7 @@ class Workspace(Mapfile):
 class MRA(object):
     def __init__(self, config_path):
         try:
-            self.config = yaml.load(open(config_path, "r"))
+            self.config = yaml.load(open(config_path, "r"), Loader=yaml.FullLoader)
         except yaml.YAMLError as e:
             exit("Error in configuration file: %s" % e)
 
@@ -987,7 +991,8 @@ class MRA(object):
     def list_fontset(self):
         try:
             return [line.split()[0] for line in open(self.get_fontset_path(), "r")]
-        except:
+        except Exception as e:
+            logging.warn('mra.py::MRA::list_fontset error %s', e)
             return []
 
     def update_fontset(self):
@@ -1039,18 +1044,18 @@ class MRA(object):
 
     def create_style(self, name, data):
         path = self.get_style_path("%s.sld" % name)
-        with open(self.mk_path(path), "w") as f:
+        with open(self.mk_path(path), "wb") as f:
             f.write(data)
         return path
 
     def get_style(self, name):
         try:
             return ET.tostring(
-                ET.parse(self.get_style_path("%s.sld" % name)).getroot())
+                ET.parse(self.get_style_path("%s.sld" % name)).getroot()).decode()
         except (OSError, IOError):
             if name in ["default_point", "default_line", "default_polygon"]:
                 return ET.tostring(ET.parse(
-                    os.path.join(os.path.dirname(__file__), "%s.sld" % name)).getroot())
+                    os.path.join(os.path.dirname(__file__), "%s.sld" % name)).getroot()).decode()
             raise KeyError(name)
 
     def delete_style(self, name):
@@ -1109,7 +1114,7 @@ class MRA(object):
         path = self.get_available_path("%s.ws.map" % name)
         try:
             return Workspace(self, path)
-        except IOError, OSError:
+        except IOError as OSError:
             raise KeyError(name)
 
     def delete_workspace(self, name):
@@ -1142,7 +1147,7 @@ class MRA(object):
     # URL Helpers:
 
     def href_parse(self, href, nb):
-        url = urlparse.urlparse(href)
+        url = urllib.parse.urlparse(href)
         parts = url.path.split("/")[-nb:]
         if parts:
             parts[-1] = parts[-1].rsplit(".", 1)[0]
@@ -1159,7 +1164,7 @@ class MRA(object):
             url += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam)
             return url
         elif "url" in cparam:
-            url = urlparse.urlparse(cparam["url"])
+            url = urllib.parse.urlparse(cparam["url"])
             if url.scheme != "file" or url.netloc:
                 raise ValueError("Only local files are suported.")
             return self.get_file_path(url.path)
diff --git a/src/mra.yaml.sample b/src/mra.yaml.sample
index 3968d3066864ca1f59a7cf36826adbe55638c933..1867c649e0b918a02294a028aaef9a4fcc414b5a 100644
--- a/src/mra.yaml.sample
+++ b/src/mra.yaml.sample
@@ -29,7 +29,7 @@ mapfile:
     units: "DD"
     # onlineresource: "http://...
     metadata:
-        ows_srs: ["EPSG:4326", "EPSG:3857"]
+        ows_srs: "EPSG:4326 EPSG:3857"
 
 debug:
     ## web_debug allows for easy debuging in the the browser, should be deactivated in production.
diff --git a/src/mralogs.py b/src/mralogs.py
index 4a9a1a2ebd12e356a894f1bfa0eb9fe2450f93de..aad56854ac49f6c095244dc2c9a07f40c96ea296 100644
--- a/src/mralogs.py
+++ b/src/mralogs.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -109,11 +106,11 @@ def logIn(level="debug", filter=(lambda *a, **kw:True)):
     def decorator(f):
         @functools.wraps(f)
         def wrapper(*args, **kwargs):
-            if filter(*args, **kwargs):
+            if list(filter(*args, **kwargs)):
                 arguments = inspect.getcallargs(getattr(f, "original_function", f), *args, **kwargs)
                 getattr(logging, level, "error")("function \"%s\" was called with args: %s" %
                                                  (f.__name__, dict([(a, short_str(v)) for a, v
-                                                                    in arguments.iteritems()])))
+                                                                    in arguments.items()])))
             return f(*args, **kwargs)
         wrapper.original_function = f
         return wrapper
@@ -137,7 +134,7 @@ def logOut(level="debug", filter=(lambda *a, **kw:True)):
         @functools.wraps(f)
         def wrapper(*args, **kwargs):
             ret = f(*args, **kwargs)
-            if filter(ret, *args, **kwargs):
+            if list(filter(ret, *args, **kwargs)):
                 getattr(logging, level, "error")("function \"%s\" returned: %s" % (f.__name__, short_str(ret)))
             return ret
         wrapper.original_function = f
diff --git a/src/pyhtml.py b/src/pyhtml.py
index 9dec3b660a2e007994dced7ffec5dba5eef8247b..7421c0953cc39f69b72405d4984036c231d61d3e 100644
--- a/src/pyhtml.py
+++ b/src/pyhtml.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -30,8 +27,8 @@
 """
 
 import pyxml
-import StringIO
-import urlparse
+import io
+import urllib.parse
 from xml.etree import ElementTree as etree
 from cgi import escape
 from xml.sax.saxutils import unescape
@@ -42,7 +39,7 @@ def should_be_url(s):
     consider a string is a URL.
 
     """
-    parsed = urlparse.urlparse(s)
+    parsed = urllib.parse.urlparse(s)
     return parsed.scheme and parsed.netloc
 
 
@@ -113,7 +110,7 @@ def dump(obj, fp, indent=0, *args, **kwargs):
 
 def dumps(obj, *args, **kwargs):
     """Returns the html representation of obj as a string."""
-    stream = StringIO.StringIO()
+    stream = io.StringIO()
     dump(obj, stream, *args, **kwargs)
     stream.flush()
     return stream.getvalue()
diff --git a/src/pyxml.py b/src/pyxml.py
index a8550a9c62cf7100802f8ddf69f4079b195f4731..27cd8f4dc5aa118857964a93c6c0490c3447af30 100644
--- a/src/pyxml.py
+++ b/src/pyxml.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -140,13 +137,13 @@ def default_xml_mapper(obj, obj_name,
         return dict_mapper(obj_name)
     elif isinstance(obj, list) or isinstance(obj, tuple):
         return list_mapper(obj_name)
-    elif any(isinstance(obj, t) for t in (basestring, int, float)):
+    elif any(isinstance(obj, t) for t in (str, int, float)):
         # Those we are sure we want to map as strings.
         return xml_string, None
     elif hasattr(obj, "__str__"):
         # Those we render as strings, but we are not sure.
         # Just add the type to the case above it is justified!
-        print "xml_mapper: Warning: We are trying to map %s as a string." % (type(obj))
+        print("xml_mapper: Warning: We are trying to map %s as a string." % (type(obj)))
         return xml_string, None
     else:
         raise NotImplementedError("Can't map %s object." % type(obj))
@@ -168,7 +165,7 @@ def xml(obj, obj_name=None, parent=None,
 
     # Create the parent if it's not provided.
     if parent is None:
-        parent = etree.Element(tag=obj_name)
+        parent = etree.Element(obj_name)
 
     mapper, hint = xml_mapper(obj, obj_name, dict_mapper, list_mapper)
     if not mapper:
@@ -198,11 +195,11 @@ def xml_dict(parent, obj, hint=None, xml_mapper=default_xml_mapper,
     The entries are of the form: <key>value</key> or <hint[0] hint[1]=key>value</hint[0]>
 
     """
-    for k, v in obj.iteritems():
+    for k, v in obj.items():
         if hint:
-            child = etree.Element(tag=hint[0], attrib={hint[1]:k})
+            child = etree.Element(hint[0], attrib={hint[1]:k})
         else:
-            child = etree.Element(tag=k, attrib={})
+            child = etree.Element(k, attrib={})
         xml(v, parent=child, xml_mapper=xml_mapper, dict_mapper=dict_mapper, list_mapper=list_mapper)
         parent.append(child)
 
@@ -215,7 +212,7 @@ def xml_list(parent, obj, hint, xml_mapper=default_xml_mapper,
 
     """
     for v in obj:
-        child = etree.Element(tag=hint, attrib={})
+        child = etree.Element(hint, attrib={})
         xml(v, parent=child, xml_mapper=xml_mapper, dict_mapper=dict_mapper, list_mapper=list_mapper)
         parent.append(child)
 
@@ -294,7 +291,8 @@ def loads(s, retname=False, *args, **kwargs):
     try:
         xml = etree.fromstring(s)
     # Python 2.6 has no xml.etree.ElementTree.ParseError.
-    except BaseException:
+    except BaseException as e:
+        logging.error("pyxml.py::loads: No XML object could be decoded. %s", e)
         raise ValueError("No XML object could be decoded.")
     o = obj(xml, *args, **kwargs)
     return (o, xml.tag) if retname else o
@@ -308,7 +306,8 @@ def load(fp, retname=False, *args, **kwargs):
     try:
         xml = etree.parse(fp)
     # Python 2.6 has no xml.etree.ElementTree.ParseError.
-    except BaseException:
+    except BaseException as e:
+        logging.error("pyxml.py::load: No XML object could be decoded. %s", e)
         raise ValueError("No XML object could be decoded.")
     o = obj(xml, *args, **kwargs)
     return (o, xml.tag) if retname else o
diff --git a/src/server.py b/src/server.py
index 082687a617ff91c6cb8c9259df2b48cc8f9d8163..551ee7807c353f0bb96801452ceb4bf069014edc 100755
--- a/src/server.py
+++ b/src/server.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -34,7 +31,7 @@ import os.path
 import sys
 import web
 import json
-import urlparse
+import urllib.parse as urlparse
 import logging
 import mralogs
 from mra import MRA
@@ -116,7 +113,7 @@ class workspaces(object):
         ws_name = data.pop("name")
 
         ws_metadata = dict(
-            ("ows_%s" % k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
+            ("ows_%s" % k, v) for k, v in data.items() if k in ["title", "abstract"])
         if "srs" in data:
             ws_metadata["ows_srs"] = " ".join(data["srs"])
 
@@ -296,7 +293,7 @@ class featuretypes(object):
 
         l_enabled = data.pop("enabled", True)
         l_metadata = dict(
-            ("ows_%s" % k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
+            ("ows_%s" % k, v) for k, v in data.items() if k in ["title", "abstract"])
 
         # Creates first the feature type:
         with webapp.mightConflict("featureType", datastore=ds_name):
@@ -428,7 +425,7 @@ class featuretype(object):
         if ft_name != data["name"]:
             raise webapp.Forbidden("Can't change the name of a feature type.")
 
-        metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
+        metadata = dict((k, v) for k, v in data.items() if k in ["title", "abstract"])
 
         with webapp.mightNotFound("featureType", datastore=ds_name):
             ws.update_featuretypemodel(ds_name, ft_name, metadata)
@@ -589,7 +586,7 @@ class coverages(object):
 
         l_enabled = data.pop("enabled", True)
         l_metadata = dict(
-            ("ows_%s" % k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
+            ("ows_%s" % k, v) for k, v in data.items() if k in ["title", "abstract"])
 
         # Creates first the coverage:
         with webapp.mightConflict("coverage", coveragestore=cs_name):
@@ -702,7 +699,7 @@ class coverage(object):
         if c_name != data["name"]:
             raise webapp.Forbidden("Can't change the name of a coverage.")
 
-        metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"])
+        metadata = dict((k, v) for k, v in data.items() if k in ["title", "abstract"])
 
         with webapp.mightNotFound("coverage", coveragestore=cs_name):
             ws.update_coveragemodel(c_name, cs_name, metadata)
@@ -968,8 +965,9 @@ class layers(object):
                 style = mra.get_style(s_name)
                 layer = mf.get_layer(l_name)
                 wslayer = wsmf.get_layer(l_name)
-            layer.add_style_sld(mf, s_name, style)
-            wslayer.add_style_sld(wsmf, s_name, style)
+            with webapp.mightFailLookup(exceptions=(Exception,)):
+                layer.add_style_sld(mf, s_name, style)
+                wslayer.add_style_sld(wsmf, s_name, style)
 
             # Remove the automatic default style.
             for s_name in layer.iter_styles():
@@ -1133,10 +1131,10 @@ class layer(object):
                 style = mra.get_style(s_name)
 
             layer.remove_style(s_name)
-            layer.add_style_sld(mf, s_name, style)
-
             wslayer.remove_style(s_name)
-            wslayer.add_style_sld(wsmf, s_name, style)
+            with webapp.mightFailLookup(exceptions=(Exception,)):
+                layer.add_style_sld(mf, s_name, style)
+                wslayer.add_style_sld(wsmf, s_name, style)
 
             # Remove the automatic default style.
             for s_name in layer.iter_styles():
@@ -1420,7 +1418,7 @@ class layergroup(object):
             raise webapp.Forbidden("Can't change the name of a layergroup.")
 
         layers = data.pop("layers", [])
-        if not isinstance(layers, list) or any(not isinstance(x, basestring) for x in layers):
+        if not isinstance(layers, list) or any(not isinstance(x, str) for x in layers):
             raise webapp.BadRequest("layers must be a list of layer names.")
 
         lg.clear()
@@ -1527,7 +1525,7 @@ class workspaceLayergroup(object):
             raise webapp.Forbidden("Can't change the name of a layergroup.")
 
         layers = data.pop("layers", [])
-        if not isinstance(layers, list) or any(not isinstance(x, basestring) for x in layers):
+        if not isinstance(layers, list) or any(not isinstance(x, str) for x in layers):
             raise webapp.BadRequest("layers must be a list of layer names.")
 
         lg.clear()
diff --git a/src/stores.py b/src/stores.py
index 5c9dc8ac2e172b51c20ee457e1d87224b6593574..088c1fff71dab7b330a871c025c8b4706c891305 100644
--- a/src/stores.py
+++ b/src/stores.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -275,7 +272,7 @@ class Featuretype(object):
         return list(self.iterfields())
 
     def iterfields(self):
-        for i in xrange(self.backend.GetLayerDefn().GetFieldCount()):
+        for i in range(self.backend.GetLayerDefn().GetFieldCount()):
             yield Field(self.backend.GetLayerDefn().GetFieldDefn(i), self)
 
     def nbfeatures(self):
@@ -284,7 +281,7 @@ class Featuretype(object):
     def iterfeatures(self, what=[], when={}):
         if what != [] or when != {}:
             raise NotImplementedError("Iterfeature doesn't support filters yet.")
-        for i in xrange(self.backend.GetFeatureCount()):
+        for i in range(self.backend.GetFeatureCount()):
             yield Feature(self.backend.GetFeature(i), self)
 
     def get_aditional_info(self):
@@ -298,7 +295,7 @@ class Featuretype(object):
                                          (schema, table))
         if not result: return
 
-        for i in xrange(result.GetFeatureCount()):
+        for i in range(result.GetFeatureCount()):
             feature = result.GetFeature(i)
             name, nullable = feature.GetField(0), feature.GetField(1)
             self.nullables[name] = nullable
@@ -339,7 +336,7 @@ class Datastore(object):
         else:
             if self.schema:
                 key = "%s.%s" % (self.schema, key)
-            item = self.backend.GetLayerByName(key.encode("ascii", "ignore"))
+            item = self.backend.GetLayerByName(key)
             if item == None: raise KeyError(key)
         return Featuretype(item, self)
 
@@ -347,7 +344,7 @@ class Datastore(object):
         return self.backend.GetLayerCount()
 
     def iterlayers(self):
-        for i in xrange(self.backend.GetLayerCount()):
+        for i in range(self.backend.GetLayerCount()):
             yield Featuretype(self.backend.GetLayerByIndex(i), self)
 
 
@@ -428,5 +425,5 @@ class Coveragestore(object):
         return list(self.iterbands())
 
     def iterbands(self):
-        for i in xrange(1, self.backend.RasterCount + 1):
+        for i in range(1, self.backend.RasterCount + 1):
             yield Band(self.backend.GetRasterBand(i))
diff --git a/src/tools.py b/src/tools.py
index 553035881f268820ef8afee23246a19df13e4c97..50f8c28eab6eefcdea9ae307e010c9b1b07af9d2 100644
--- a/src/tools.py
+++ b/src/tools.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
diff --git a/src/webapp.py b/src/webapp.py
index 8193b4506268305c8c41f2435f0988a60afdcfd6..6d03ad41c4ac9e23b88f5852ac7d1f433d3f7ad1 100644
--- a/src/webapp.py
+++ b/src/webapp.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -38,6 +35,7 @@ import functools
 import os.path
 import itertools
 import mralogs
+import logging
 
 
 class KeyExists(KeyError):
@@ -253,7 +251,7 @@ class URLMap(object):
         if not last_is_var:
             url += "(?:/|(\\.[^/.]+)?)"
 
-        self.map.extend((url, page if isinstance(page, basestring) else page.__name__))
+        self.map.extend((url, page if isinstance(page, str) else page.__name__))
 
     def __getattr__(self, name):
         """Maps all attributes to a wrapper function calling self(name, *args, **kwargs),
@@ -377,7 +375,7 @@ class HTTPCompatible(object):
 
             args = list(args)
 
-            if isinstance(args[-1], basestring):
+            if isinstance(args[-1], str):
                 last = args[-1].split(".")
                 if len(last) == 1:
                     last.append(self.default)
@@ -424,13 +422,14 @@ class HTTPCompatible(object):
             try:
                 content = f(*args, **kwargs)
             except BaseException as e:
+                logging.warn("webapp.py::HTTPCompatible Unknown Error : %s", e)
                 raise
 
             name_hint = self.name_hint
 
             if name_hint is None and isinstance(content, dict) and len(content) == 1:
-                name_hint = next(content.iterkeys())
-                content = next(content.itervalues())
+                name_hint = next(iter(content.keys()))
+                content = next(iter(content.values()))
             elif name_hint is None:
                 name_hint = "response"
 
@@ -477,11 +476,11 @@ def get_data(name=None, mandatory=[], authorized=[], forbidden=[]):
     try:
         if "text/xml" in ctype or "application/xml" in ctype:
             data, dname = pyxml.loads(data, retname=True)
-            print "received \"%s\"" % dname
-            print data
+            print("received \"%s\"" % dname)
+            print(data)
             if name and dname != name: data = None
         elif "application/json" in ctype:
-            data = json.loads(data)
+            data = json.loads(data.decode())
             if name: data = data.get(name, None)
         else:
             raise web.badrequest("Content-type \"%s\" is not allowed." % ctype)
diff --git a/tests/__init__.py b/tests/__init__.py
index 3891fd0fa2416ca90e96b116ddeabe1d62187842..27b06b41f8163aea594658c73b1cc1286ed07a50 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -23,4 +20,3 @@
 #   GNU General Public License for more details.                        #
 #                                                                       #
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
diff --git a/tests/testScenario.py b/tests/testScenario.py
index 7f22ba56848035ffa33e041575472e5a46cac30f..5fc5a4e966002fd6d5fc071930e7d656990ab656 100644
--- a/tests/testScenario.py
+++ b/tests/testScenario.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
diff --git a/tests/utils.py b/tests/utils.py
index d6d92507c94fd1d5c5e5ae65b2110190fc4b3b5c..41ac318b63f9f87d81153fc5be8babe579038d1d 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,6 +1,3 @@
-#!/usr/bin/env python2.7
-# -*- coding: utf-8 -*-
-
 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
 #                                                                       #
 #   MapServer REST API is a python wrapper around MapServer which       #
@@ -8,7 +5,7 @@
 #   developped to match as close as possible the way the GeoServer      #
 #   REST API acts.                                                      #
 #                                                                       #
-#   Copyright (C) 2011-2013 Neogeo Technologies.                        #
+#   Copyright (C) 2011-2020 Neogeo Technologies.                        #
 #                                                                       #
 #   This file is part of MapServer Rest API.                            #
 #                                                                       #
@@ -88,5 +85,3 @@ def APIRequest(method, url, data=None, encode=default_encoding, decode=default_e
     assert 200 <= r.status < 300, recv
 
     return (recv, r) if get_response else recv
-
-