diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..5d5d73a8c339f977d0634681b529ec07e77f8380 --- /dev/null +++ b/setup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# 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-2013 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. # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + +import sys +from distutils.core import setup + +setup( + name='MapServer Rest API', + version='0.1.0', + author='Neogeo Technologies', + author_email='contact@neogeo-online.net', + description='A RESTFul interface for MapServer', + long_description=file('README.rst','rb').read(), + keywords='neogeo mapserver rest restful', + license="GPLv3", + #url='', + classifiers=[ + 'Development Status :: Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: GPLv3', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Natural Language :: English', + 'Topic :: Scientific/Engineering :: GIS', + ], + #packages=, + #package_dir={'':'src'}, + #namespace_packages=['mra'], + requires=[ + 'web.py', + 'pyyaml', + 'osgeo', + ], + scripts=[ + 'src/server.py', + ] + ) diff --git a/src/server.py b/src/server.py index 6b052199b70f67e6c4fa71e661c85d007a39c399..c04661a46f79f8fdfac8895010d44d9a431f27bf 100755 --- a/src/server.py +++ b/src/server.py @@ -86,12 +86,13 @@ class mapfiles(object): return {"mapfiles": mapfiles} + @HTTPCompatible() def POST(self, map_name, format): data = get_data() # TODO: Create mapfile raise NotImplemented() - webapp.Created("%s/maps/%s%s" % (web.ctx.home, map_name, format or "")) + webapp.Created("%s/maps/%s%s" % (web.ctx.home, map_name, (".%s" % format) if format else "")) class named_mapfile(object): @@ -147,6 +148,7 @@ class datastores(object): } for ds_name in ws.iter_datastore_names()] } + @HTTPCompatible() def POST(self, map_name, ws_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -158,7 +160,7 @@ class datastores(object): ws.save() webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s%s" % ( - web.ctx.home, map_name, ws_name, ds_name, format or "")) + web.ctx.home, map_name, ws_name, ds_name, (".%s" % format) if format else "")) class datastore(object): @@ -174,6 +176,7 @@ class datastore(object): info["connectionParameters"] = Entries(info["connectionParameters"], tag_name="entry", key_name="key") return {"dataStore": info} + @HTTPCompatible() def PUT(self, map_name, ws_name, ds_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -185,6 +188,7 @@ class datastore(object): ws.update_datastore(ds_name, data) ws.save() + @HTTPCompatible() def DELETE(self, map_name, ws_name, ds_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -207,6 +211,7 @@ class featuretypes(object): } for ft in ws.iter_featuretypemodels(ds_name)] } + @HTTPCompatible() def POST(self, map_name, ws_name, ds_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -217,7 +222,7 @@ class featuretypes(object): ws.save() webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s/featuretypes/%s%s" % ( - web.ctx.home, map_name, ws.name, ds_name, data["name"], format or "")) + web.ctx.home, map_name, ws.name, ds_name, data["name"], (".%s" % format) if format else "")) class featuretype(object): @@ -280,6 +285,7 @@ class featuretype(object): }) } + @HTTPCompatible() def PUT(self, map_name, ws_name, ds_name, ft_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -293,6 +299,7 @@ class featuretype(object): ws.update_featuretypemodel(ft_name, ds_name, metadata) ws.save() + @HTTPCompatible() def DELETE(self, map_name, ws_name, ds_name, ft_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -317,6 +324,7 @@ class coveragestores(object): } for cs_name in ws.iter_coveragestore_names()] } + @HTTPCompatible() def POST(self, map_name, ws_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -328,7 +336,7 @@ class coveragestores(object): ws.save() webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s%s" % ( - web.ctx.home, map_name, ws_name, cs_name, format or "")) + web.ctx.home, map_name, ws_name, cs_name, (".%s" % format) if format else "")) class coveragestore(object): @@ -343,6 +351,7 @@ class coveragestore(object): info["connectionParameters"] = Entries(info["connectionParameters"], tag_name="entry", key_name="key") return {"coverageStore": info} + @HTTPCompatible() def PUT(self, map_name, ws_name, cs_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -354,6 +363,7 @@ class coveragestore(object): ws.update_coveragestore(cs_name, data) ws.save() + @HTTPCompatible() def DELETE(self, map_name, ws_name, cs_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -377,6 +387,7 @@ class coverages(object): } for c in ws.iter_coveragemodels(cs_name)] } + @HTTPCompatible() def POST(self, map_name, ws_name, cs_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -387,7 +398,7 @@ class coverages(object): ws.save() webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s/coverages/%s%s" % ( - web.ctx.home, map_name, ws.name, cs_name, data["name"], format or "")) + web.ctx.home, map_name, ws.name, cs_name, data["name"], (".%s" % format) if format else "")) class coverage(object): @@ -437,6 +448,7 @@ class coverage(object): }) } + @HTTPCompatible() def PUT(self, map_name, ws_name, cs_name, c_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -451,6 +463,7 @@ class coverage(object): ws.update_coveragemodel(c_name, cs_name, metadata) ws.save() + @HTTPCompatible() def DELETE(self, map_name, ws_name, cs_name, c_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) @@ -465,6 +478,7 @@ class coverage(object): class files(object): + @HTTPCompatible(allow_all=True) def PUT(self, map_name, ws_name, st_type, st_name, f_type, format): import zipfile @@ -535,6 +549,7 @@ class styles(object): } for s_name in tools.iter_styles(mf)] } + @HTTPCompatible() def POST(self, map_name, format): mf = get_mapfile(map_name) @@ -581,6 +596,7 @@ class style(object): "href": "%s/maps/%s/styles/%s.sld" % (web.ctx.home, map_name, s_name) } + @HTTPCompatible() def PUT(self, map_name, s_name, format): path = tools.get_style_path(s_name) try: @@ -597,6 +613,7 @@ class style(object): f.write(data) + @HTTPCompatible() def DELETE(self, map_name, s_name, format): mf = get_mapfile(map_name) @@ -634,6 +651,7 @@ class layers(object): } for layer in mf.iter_layers()] } + @HTTPCompatible() def POST(self, map_name, format): data = get_data(name="layer", mandatory=["name", "resource"]) @@ -666,7 +684,7 @@ class layers(object): model.create_layer(ws, mf, l_name, l_enabled) mf.save() - webapp.Created("%s/maps/%s/layers/%s%s" % (web.ctx.home, map_name, l_name, format or "")) + webapp.Created("%s/maps/%s/layers/%s%s" % (web.ctx.home, map_name, l_name, (".%s" % format) if format else "")) class layer(object): @@ -704,6 +722,7 @@ class layer(object): }) } + @HTTPCompatible() def PUT(self, map_name, l_name, format): mf = get_mapfile(map_name) @@ -735,7 +754,7 @@ class layer(object): model.configure_layer(layer, ws, l_enabled) mf.save() - + @HTTPCompatible() def DELETE(self, map_name, l_name, format): mf = get_mapfile(map_name) with webapp.mightNotFound("layer", mapfile=map_name): @@ -759,6 +778,7 @@ class layerstyles(object): } for s_name in layer.iter_styles()], } + @HTTPCompatible() def POST(self, map_name, l_name, format): data = get_data(name="style", mandatory=["resource"]) @@ -790,10 +810,11 @@ class layerstyles(object): layer.add_style_sld(mf, s_name, style) mf.save() - webapp.Created("%s/maps/%s/layers/%s/layerstyles/%s%s" % (web.ctx.home, map_name, l_name, s_name, format or "")) + webapp.Created("%s/maps/%s/layers/%s/layerstyles/%s%s" % (web.ctx.home, map_name, l_name, s_name, (".%s" % format) if format else "")) class layerstyle(object): + @HTTPCompatible() def DELETE(self, map_name, l_name, s_name, format): mf = get_mapfile(map_name) with webapp.mightNotFound("layer", mapfile=map_name): @@ -828,6 +849,7 @@ class layergroups(object): } for lg in mf.iter_layergroups()] } + @HTTPCompatible() def POST(self, map_name, format): mf = get_mapfile(map_name) @@ -841,7 +863,7 @@ class layergroups(object): mf.save() - webapp.Created("%s/maps/%s/layergroups/%s%s" % (web.ctx.home, map_name, lg.name, format or "")) + webapp.Created("%s/maps/%s/layergroups/%s%s" % (web.ctx.home, map_name, lg.name, (".%s" % format) if format else "")) class layergroup(object): @@ -874,6 +896,7 @@ class layergroup(object): }) } + @HTTPCompatible() def PUT(self, map_name, lg_name, format): mf = get_mapfile(map_name) @@ -893,6 +916,7 @@ class layergroup(object): mf.save() + @HTTPCompatible() def DELETE(self, map_name, lg_name, format): mf = get_mapfile(map_name) diff --git a/src/webapp.py b/src/webapp.py index 1e4c41b8ac2c164466c9adab6b325e75bebe8c9b..98f12ee45ca7a77df2e8ecec5dee49abbfe2b952 100644 --- a/src/webapp.py +++ b/src/webapp.py @@ -293,7 +293,7 @@ class HTTPCompatible(object): } def __init__(self, authorize=set(), forbid=set(), authorized=set(["xml", "json", "html"]), - default="html", renderer=default_renderer, render=None, + allow_all=False, default="html", renderer=default_renderer, render=None, parse_format=True, trim_nones=True, name_hint="MapServerRESTAPI_Response"): """Decorator factory used to transform the output of a backend function @@ -302,6 +302,7 @@ class HTTPCompatible(object): authorize and forbid are filters that allow for easy modification of authorized, which is used to check if the request format is OK. + If allow_all is True it cancels the whole authorization process for formats. default indicates which format should be used if not specified by the client. renderer is the function called for formating. render can be set to True or False to prevent HTTPCompatible from guessing if we want output or not @@ -329,6 +330,7 @@ class HTTPCompatible(object): # Computes authorized formats, makes sure default is one of them. self.authorized = authorize | (authorized - forbid) + self.allow_all = allow_all # Should we trim trailing Nones ? self.trim_nones = trim_nones @@ -347,6 +349,8 @@ class HTTPCompatible(object): @functools.wraps(f) def wrapper(*args, **kwargs): + print "This is handled by HTTPCompatible." + args = list(args) # If the last argument is a string starting with "." we use it as format @@ -373,7 +377,7 @@ class HTTPCompatible(object): del args[-1] # Check format against authorized. - if page_format not in self.authorized: + if not self.allow_all and page_format not in self.authorized: raise NotFound() # Insert the format in the argument list where it is expected by f. diff --git a/tests/testScenario.py b/tests/testScenario.py index 1fc4cd453f51fc3129df4543621254895eca9f19..98a3a54f0fdd862e16fb54fddc2a74c085be4d6f 100644 --- a/tests/testScenario.py +++ b/tests/testScenario.py @@ -24,6 +24,7 @@ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +import utils from utils import APIRequest import sys @@ -358,6 +359,8 @@ def _test_layers(target, map_name): def test_scenario(): + # utils.default_encoding = "xml" + target = "http://localhost:8080" map_name = "tests" diff --git a/tests/utils.py b/tests/utils.py index 73e4491dfb8f438946638228e88b8e454844f023..d6d92507c94fd1d5c5e5ae65b2110190fc4b3b5c 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -31,6 +31,8 @@ import json import sys +default_encoding = "json" + def deduce_content_type(type): if type == "json": return "application/json" @@ -38,7 +40,7 @@ def deduce_content_type(type): return "application/xml" -def APIRequest(method, url, data=None, encode="json", decode="json", content_type=None, expected_type=None, +def APIRequest(method, url, data=None, encode=default_encoding, decode=default_encoding, content_type=None, expected_type=None, get_response=False): if encode == "json":