diff --git a/requirements.txt b/requirements.txt index 2bc7e63f4704bdabe6d3ed5e4ad35290ddc4aa4b..9ff49cf8eaa769cafbea8111388fb757435b319d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -web.py>=0.50,<0.60 +web.py>=0.60,<0.70 gdal<2.5.0 pyyaml<5.4 diff --git a/setup.py b/setup.py index 040b338185fd80e832efee96ab30bf6334377f2a..a5dd413769965e368cc19d856ce011a9092d8f12 100644 --- a/setup.py +++ b/setup.py @@ -24,9 +24,7 @@ import os.path from setuptools import setup - - -version = '1.1.8' +from src import __version__ def parse_requirements(filename): @@ -42,7 +40,7 @@ reqs = [str(req) for req in parse_requirements(reqs_filename)] setup( name="MapServer Rest API", - version=version, + version=__version__, description="A RESTFul interface for MapServer", author="Neogeo Technologies", author_email="contact@neogeo.fr", diff --git a/src/__init__.py b/src/__init__.py index 27b06b41f8163aea594658c73b1cc1286ed07a50..dbb7b4e14859dadf081d091f9bcc3d3e92a29e69 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -20,3 +20,6 @@ # GNU General Public License for more details. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +__version__ = '1.1.9' diff --git a/src/mra.py b/src/mra.py index 1eb99ff0eda2dc84700754c5fce7dda6775b859f..06e9f08518dbf2f6c09c715fb73bfcb365228094 100644 --- a/src/mra.py +++ b/src/mra.py @@ -96,7 +96,7 @@ OUTPUTFORMAT = { imagemode=mapscript.MS_IMAGEMODE_FEATURE, transparent=mapscript.MS_OFF, options={"FORM": "SIMPLE", "STORAGE": "stream"}) - }, + }, 'WMS': { 'PNG8': outputformat( "AGG/PNG8", "png8", mimetype="image/png; mode=8bit", @@ -109,8 +109,8 @@ OUTPUTFORMAT = { "AGG/JPEG", "jpeg", mimetype="image/jpeg", imagemode=mapscript.MS_IMAGEMODE_RGB, extension="jpg", options={"GAMMA": "0.75"}) - } } +} class MetadataMixin(object): @@ -134,7 +134,8 @@ class Layer(MetadataMixin): self.ms = backend def enable(self, enabled=True): - wms = ("GetCapabilities", "GetMap", "GetFeatureInfo", "GetLegendGraphic", "DescribeLayer", "GetStyles") + wms = ("GetCapabilities", "GetMap", "GetFeatureInfo", + "GetLegendGraphic", "DescribeLayer", "GetStyles") wcs = ("GetCapabilities", "GetCoverage", "DescribeCoverage") wfs = ("GetCapabilities", "GetFeature", "DescribeFeatureType") @@ -147,11 +148,14 @@ class Layer(MetadataMixin): self.set_metadata("wfs_enable_request", " ".join(wfs)) else: self.ms.status = mapscript.MS_OFF - self.set_metadata("wms_enable_request", " ".join(["!%s" % m for m in wms])) + self.set_metadata("wms_enable_request", + " ".join(["!%s" % m for m in wms])) if self.ms.type == 3: - self.set_metadata("wcs_enable_request", " ".join(["!%s" % m for m in wcs])) + self.set_metadata("wcs_enable_request", + " ".join(["!%s" % m for m in wcs])) else: - self.set_metadata("wfs_enable_request", " ".join(["!%s" % m for m in wfs])) + self.set_metadata("wfs_enable_request", + " ".join(["!%s" % m for m in wfs])) def get_type_name(self): return { @@ -160,7 +164,7 @@ class Layer(MetadataMixin): 2: "POLYGON", 3: "RASTER", 4: "ANNOTATION", - }[self.ms.type] + }[self.ms.type] def get_proj4(self): return self.ms.getProjection() @@ -232,7 +236,8 @@ class Layer(MetadataMixin): xmlsld.firstChild.getElementsByTagNameNS("*", "NamedLayer")[0]\ .getElementsByTagNameNS("*", "Name")[0].firstChild.data = sld_layer_name except Exception as e: - logging.error("mra.py::Layer::add_style_sld: Bad sld (No NamedLayer/Name): %s", e) + logging.error( + "mra.py::Layer::add_style_sld: Bad sld (No NamedLayer/Name): %s", e) raise ValueError("Bad sld (No NamedLayer/Name)") new_sld = xmlsld.toxml() @@ -245,7 +250,8 @@ class Layer(MetadataMixin): try: 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) + logging.error( + "mra.py::Layer::add_style_sld: Unable to access storage : %s", e) raise ValueError("Unable to access storage.") for i in range(ms_template_layer.numclasses): @@ -270,9 +276,11 @@ class Layer(MetadataMixin): return None try: - style = open(os.path.join(os.path.dirname(__file__), "%s.sld" % s_name), encoding="utf-8").read() + 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) + logging.warning( + "mra.py::Layer::set_default_style IOError %s", OSError) return self.add_style_sld(mf, s_name, style) @@ -379,7 +387,8 @@ class Mapfile(MetadataMixin): self.set_metadata("%s_enable_request" % ows, "*") if 'onlineresource' in config: - onlineresource = urljoin(config.get('onlineresource'), self.ms.name) + onlineresource = urljoin(config.get( + 'onlineresource'), self.ms.name) self.set_metadata('ows_onlineresource', onlineresource) fontset and self.ms.setFontSet(fontset) @@ -443,7 +452,7 @@ class Mapfile(MetadataMixin): # Add metadata. metadata = { "wms_srs": self.get_metadata("ows_srs"), - } + } metadata.update(l_metadata) layer.update_metadatas(metadata) @@ -534,8 +543,10 @@ class FeatureTypeModel(LayerModel): cparam = info["connectionParameters"] if cparam.get("dbtype", None) in ["postgis", "postgres", "postgresql"]: self.ms.connectiontype = mapscript.MS_POSTGIS - connection = "dbname=%s port=%s host=%s " % (cparam.get("database", "postgres"), cparam.get("port", "5432"), cparam.get("host", "localhost")) - connection += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam) + connection = "dbname=%s port=%s host=%s " % (cparam.get( + "database", "postgres"), cparam.get("port", "5432"), cparam.get("host", "localhost")) + connection += " ".join("%s=%s" % (p, cparam[p]) + for p in ["user", "password"] if p in cparam) self.ms.connection = connection self.ms.data = '%s FROM %s.%s' % ( ds[ft_name].get_geometry_column(), @@ -554,7 +565,8 @@ class FeatureTypeModel(LayerModel): # Update mra metadata, and make sure the mandatory ones are left untouched. self.update_mra_metadatas(metadata) - self.update_mra_metadatas({"name": ft_name, "type": "featuretype", "storage": ds_name}) + self.update_mra_metadatas( + {"name": ft_name, "type": "featuretype", "storage": ds_name}) def configure_layer(self, layer, enabled=True): ws = self.ws @@ -578,7 +590,7 @@ class FeatureTypeModel(LayerModel): "type": self.get_mra_metadata("type"), "storage": self.get_mra_metadata("storage"), "workspace": ws.name, - }) + }) layer.enable(enabled) @@ -596,7 +608,7 @@ class FeatureTypeModel(LayerModel): "gml_%s_type" % field_name: field.get_type_gml(), # "gml_%s_precision" % field_name # "gml_%s_width" % field_name - }) + }) geometry_column = ft.get_geometry_column() if geometry_column is None: @@ -611,7 +623,7 @@ class FeatureTypeModel(LayerModel): self.ms.extent.miny, self.ms.extent.maxx, self.ms.extent.maxy, - ), + ), "ows_include_items": ",".join(field_names), "gml_include_items": ",".join(field_names), "gml_geometries": geometry_column, @@ -619,13 +631,13 @@ class FeatureTypeModel(LayerModel): # TODO: Add gml_<geometry name>_occurances, "wfs_srs": ws.get_metadata("ows_srs"), "wfs_getfeature_formatlist": ",".join(list(OUTPUTFORMAT["WFS"].keys())) - }) + }) if ft.get_fid_column() is not None: layer.set_metadatas({ "wfs_featureid": ft.get_fid_column(), "gml_featureid": ft.get_fid_column(), - }) + }) plugins.extend("post_configure_vector_layer", self, ws, ds, ft, layer) @@ -645,7 +657,8 @@ class CoverageModel(LayerModel): try: crs = metadata.pop("crs") - proj4 = "+init=%s:%s" % (crs["authority_name"], crs["authority_code"]) + proj4 = "+init=%s:%s" % (crs["authority_name"], + crs["authority_code"]) except Exception as e: logging.warn('mra.py::MRA::CoverageModel.update error %s', e) proj4 = cs.get_proj4() @@ -672,12 +685,12 @@ class CoverageModel(LayerModel): url = urlparse(cparam["url"]) filename = self.ws.mra.get_file_path(url.path) if cs.tindex is None: - #if cparam["dbtype"] in ["tif", "tiff"]: + # if cparam["dbtype"] in ["tif", "tiff"]: self.ms.data = filename self.ms.tileindex = None self.ms.tileitem = None # TODO: strip extention. - #else: + # else: # raise ValueError("Unhandled type \"%s\"." % cparam["dbtype"]) else: self.ms.data = None @@ -719,7 +732,7 @@ class CoverageModel(LayerModel): "type": self.get_mra_metadata("type"), "storage": self.get_mra_metadata("storage"), "workspace": ws.name, - }) + }) layer.set_metadatas({ "ows_name": layer_name, @@ -730,11 +743,11 @@ class CoverageModel(LayerModel): self.ms.extent.miny, self.ms.extent.maxx, self.ms.extent.maxy, - ), + ), "wcs_name": layer.get_metadata("wcs_name", None) or layer_name, "wcs_label": layer.get_metadata("wcs_label", None) or layer_name, "wcs_description": layer.get_metadata("wcs_description", None) or layer_name - }) + }) layer.enable(enabled) @@ -843,7 +856,8 @@ class Workspace(Mapfile): 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) + raise ValueError( + "The datastore \"%s\" can't be delete because it is used." % name) return self.delete_store("datastore", name) # Coveragestores (this is c/p from datastores): @@ -886,7 +900,8 @@ class Workspace(Mapfile): 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) + raise ValueError( + "The coveragestore \"%s\" can't be delete because it is used." % name) return self.delete_store("coveragestore", name) # LayerModels: @@ -945,7 +960,8 @@ class Workspace(Mapfile): def delete_layermodel(self, st_type, ds_name, ft_name): lm = self.get_layermodel(st_type, ds_name, ft_name) if lm.get_mra_metadata("layers", []): - raise ValueError("The %s \"%s\" can't be delete because it is used." % (st_type, ft_name)) + raise ValueError( + "The %s \"%s\" can't be delete because it is used." % (st_type, ft_name)) self.ms.removeLayer(lm.ms.index) # Featuretypes @@ -998,7 +1014,7 @@ class MRA(object): self.config = yaml.load( open(config_path, "r"), # Loader=yaml.FullLoader - ) + ) except yaml.YAMLError as e: exit("Error in configuration file: %s" % e) @@ -1025,7 +1041,8 @@ class MRA(object): return os.path.relpath(path, self.get_path()) def get_resource_path(self, *args): - root = self.config["storage"].get("resources", self.get_path("resources")) + root = self.config["storage"].get( + "resources", self.get_path("resources")) return self.get_path(root, *args) def pub_resource_path(self, path): @@ -1052,7 +1069,8 @@ class MRA(object): fontset.close def get_font_path(self, *args): - root = self.config["storage"].get("fonts", self.get_resource_path("fonts")) + root = self.config["storage"].get( + "fonts", self.get_resource_path("fonts")) return self.get_resource_path(root, *args) def create_font(self, name, data=None): @@ -1074,7 +1092,8 @@ class MRA(object): # Styles: def get_style_path(self, *args): - root = self.config["storage"].get("styles", self.get_resource_path("styles")) + root = self.config["storage"].get( + "styles", self.get_resource_path("styles")) return self.get_resource_path(root, *args) def pub_style_path(self, path): @@ -1118,7 +1137,8 @@ class MRA(object): # Files: def get_file_path(self, *args): - root = self.config["storage"].get("data", self.get_resource_path("data")) + root = self.config["storage"].get( + "data", self.get_resource_path("data")) return self.get_resource_path(root, *args) def pub_file_path(self, path): @@ -1134,7 +1154,8 @@ class MRA(object): # Available (get): def get_available_path(self, *args): - root = self.config["storage"].get("available", self.get_path("available")) + root = self.config["storage"].get( + "available", self.get_path("available")) return self.get_path(root, *args) def pub_available_path(self, path): @@ -1169,12 +1190,14 @@ class MRA(object): def delete_workspace(self, name): # path = self.get_available_path("%s.ws.map" % name) - raise NotImplementedError("Method 'delete_workspace' is not yet available. (TODO)") + raise NotImplementedError( + "Method 'delete_workspace' is not yet available. (TODO)") # Services: def get_service_path(self, *args): - root = self.config["storage"].get("services", self.get_path("services")) + root = self.config["storage"].get( + "services", self.get_path("services")) return self.get_path(root, *args) def pub_service_path(self, path): @@ -1210,9 +1233,11 @@ class MRA(object): cparam = info["connectionParameters"] if cparam.get("dbtype", "") == "postgis": # First mandatory - url = "PG:dbname=%s port=%s host=%s " % (cparam["database"], cparam["port"], cparam["host"]) + url = "PG:dbname=%s port=%s host=%s " % ( + cparam["database"], cparam["port"], cparam["host"]) # Then optionals: - url += " ".join("%s=%s" % (p, cparam[p]) for p in ["user", "password"] if p in cparam) + url += " ".join("%s=%s" % (p, cparam[p]) + for p in ["user", "password"] if p in cparam) return url elif "url" in cparam: url = urlparse(cparam["url"]) @@ -1220,4 +1245,5 @@ class MRA(object): raise ValueError("Only local files are suported.") return self.get_file_path(url.path) else: - raise ValueError("Unhandled type \"%s\"." % cparam.get("dbtype", "<unknown>")) + raise ValueError("Unhandled type \"%s\"." % + cparam.get("dbtype", "<unknown>")) diff --git a/src/server.py b/src/server.py index 3e459d37e2a45dc4ac05051bbaafc87ed2eebce0..d354a8fbc9eaf0af569be7ba33c2a1e85524ebb3 100755 --- a/src/server.py +++ b/src/server.py @@ -44,6 +44,7 @@ import webapp from webapp import get_data from webapp import HTTPCompatible from webapp import urlmap +from src import __version__ # Some helper functions first. @@ -64,6 +65,7 @@ class index(object): @HTTPCompatible(authorized=["html"]) def GET(self, format): return { + "version": href("version"), "about/version": href("about/version"), "workspaces": href("workspaces"), "styles": href("styles"), @@ -76,6 +78,15 @@ class index(object): } +class app_version(object): + """To know about application version + + """ + @HTTPCompatible() + def GET(self, format): + return {"mra": __version__} + + class version(object): """To know about used versions... @@ -1661,6 +1672,8 @@ class OWSWorkspaceSettings(object): # Index: urlmap(index, "") +# App Version: +urlmap(app_version, "version") # About version: urlmap(version, "about", "version") # Workspaces: