Hello community, here is the log from the commit of package python-WSME for openSUSE:Factory checked in at 2015-09-11 09:04:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-WSME (Old) and /work/SRC/openSUSE:Factory/.python-WSME.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-WSME" Changes: -------- --- /work/SRC/openSUSE:Factory/python-WSME/python-WSME.changes 2015-06-03 08:49:19.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-WSME.new/python-WSME.changes 2015-09-11 09:04:40.000000000 +0200 @@ -1,0 +2,11 @@ +Wed Sep 9 07:12:25 UTC 2015 - tbecht...@suse.com + +- update to 0.8.0: + * Return 400, if the query string is not a dict + * rest: return 415 when content-type is invalid + * json: raise ValueError invalid list or dict + * Fixes exception path with the datatype is a Object + * Update README formatting for release tools + * Set up dependencies for cross-tests + +------------------------------------------------------------------- Old: ---- WSME-0.7.0.tar.gz New: ---- WSME-0.8.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-WSME.spec ++++++ --- /var/tmp/diff_new_pack.wCdf2E/_old 2015-09-11 09:04:41.000000000 +0200 +++ /var/tmp/diff_new_pack.wCdf2E/_new 2015-09-11 09:04:41.000000000 +0200 @@ -17,7 +17,7 @@ Name: python-WSME -Version: 0.7.0 +Version: 0.8.0 Release: 0 Summary: Web Services Made Easy License: MIT ++++++ WSME-0.7.0.tar.gz -> WSME-0.8.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/AUTHORS new/WSME-0.8.0/AUTHORS --- old/WSME-0.7.0/AUTHORS 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/AUTHORS 2015-08-25 17:05:53.000000000 +0200 @@ -21,6 +21,7 @@ Louis Taylor <lo...@kragniz.eu> Lucas Alvares Gomes <lucasago...@gmail.com> Mehdi Abaakouk <mehdi.abaak...@enovance.com> +Mehdi Abaakouk <sil...@sileht.net> Michael Krotscheck <krotsch...@gmail.com> Ryan Petrello <li...@ryanpetrello.com> Sascha Peilicke <speili...@suse.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/ChangeLog new/WSME-0.8.0/ChangeLog --- old/WSME-0.7.0/ChangeLog 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/ChangeLog 2015-08-25 17:05:53.000000000 +0200 @@ -1,6 +1,16 @@ CHANGES ======= +0.8.0 +----- + +* Return 400, if the query string is not a dict +* rest: return 415 when content-type is invalid +* json: raise ValueError invalid list or dict +* Fixes exception path with the datatype is a Object +* Update README formatting for release tools +* Set up dependencies for cross-tests + 0.7.0 ----- @@ -9,6 +19,7 @@ * Add pytz as a dependency * Fix wrong reference to status argument in the docs * Added timezone support to parse_isodatetime +* Complex types should check unexpected attributes * Replace deprecated assertEquals with assertEqual * Update changes doc * Ensure UserType objects are converted to basetype diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/PKG-INFO new/WSME-0.8.0/PKG-INFO --- old/WSME-0.7.0/PKG-INFO 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/PKG-INFO 2015-08-25 17:05:53.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: WSME -Version: 0.7.0 +Version: 0.8.0 Summary: Simplify the writing of REST APIs, and extend them with additional protocols. Home-page: UNKNOWN Author: "Christophe de Vienne" @@ -108,9 +108,10 @@ Contribute ~~~~~~~~~~ - :Report issues: `WSME issue tracker`_ - :Source code: git clone https://github.com/stackforge/wsme/ - :Gerrit: https://review.openstack.org/#/q/project:stackforge/wsme,n,z/ + * Documentation: http://packages.python.org/WSME/ + * Source: http://git.openstack.org/cgit/stackforge/wsme + * Bugs: https://bugs.launchpad.net/wsme/+bugs + * Code review: https://review.openstack.org/#/q/project:stackforge/wsme,n,z .. _Changelog: http://packages.python.org/WSME/changes.html .. _python-wsme mailinglist: http://groups.google.com/group/python-wsme diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/README.rst new/WSME-0.8.0/README.rst --- old/WSME-0.7.0/README.rst 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/README.rst 2015-08-25 17:05:33.000000000 +0200 @@ -100,9 +100,10 @@ Contribute ~~~~~~~~~~ -:Report issues: `WSME issue tracker`_ -:Source code: git clone https://github.com/stackforge/wsme/ -:Gerrit: https://review.openstack.org/#/q/project:stackforge/wsme,n,z/ +* Documentation: http://packages.python.org/WSME/ +* Source: http://git.openstack.org/cgit/stackforge/wsme +* Bugs: https://bugs.launchpad.net/wsme/+bugs +* Code review: https://review.openstack.org/#/q/project:stackforge/wsme,n,z .. _Changelog: http://packages.python.org/WSME/changes.html .. _python-wsme mailinglist: http://groups.google.com/group/python-wsme diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/WSME.egg-info/PKG-INFO new/WSME-0.8.0/WSME.egg-info/PKG-INFO --- old/WSME-0.7.0/WSME.egg-info/PKG-INFO 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/WSME.egg-info/PKG-INFO 2015-08-25 17:05:53.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: WSME -Version: 0.7.0 +Version: 0.8.0 Summary: Simplify the writing of REST APIs, and extend them with additional protocols. Home-page: UNKNOWN Author: "Christophe de Vienne" @@ -108,9 +108,10 @@ Contribute ~~~~~~~~~~ - :Report issues: `WSME issue tracker`_ - :Source code: git clone https://github.com/stackforge/wsme/ - :Gerrit: https://review.openstack.org/#/q/project:stackforge/wsme,n,z/ + * Documentation: http://packages.python.org/WSME/ + * Source: http://git.openstack.org/cgit/stackforge/wsme + * Bugs: https://bugs.launchpad.net/wsme/+bugs + * Code review: https://review.openstack.org/#/q/project:stackforge/wsme,n,z .. _Changelog: http://packages.python.org/WSME/changes.html .. _python-wsme mailinglist: http://groups.google.com/group/python-wsme diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/WSME.egg-info/pbr.json new/WSME-0.8.0/WSME.egg-info/pbr.json --- old/WSME-0.7.0/WSME.egg-info/pbr.json 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/WSME.egg-info/pbr.json 2015-08-25 17:05:53.000000000 +0200 @@ -1 +1 @@ -{"git_version": "43e125d", "is_release": true} \ No newline at end of file +{"git_version": "1dc4421", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/setup.cfg new/WSME-0.8.0/setup.cfg --- old/WSME-0.7.0/setup.cfg 2015-06-01 15:19:45.000000000 +0200 +++ new/WSME-0.8.0/setup.cfg 2015-08-25 17:05:53.000000000 +0200 @@ -42,6 +42,6 @@ [egg_info] tag_date = 0 -tag_build = tag_svn_revision = 0 +tag_build = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/tox-tmpl.ini new/WSME-0.8.0/tox-tmpl.ini --- old/WSME-0.7.0/tox-tmpl.ini 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/tox-tmpl.ini 2015-08-25 17:05:33.000000000 +0200 @@ -126,5 +126,7 @@ [testenv:venv] commands = {posargs} usedevelop=True -deps= - pbr +deps = + pbr + oslo.config + oslotest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/tox.ini new/WSME-0.8.0/tox.ini --- old/WSME-0.7.0/tox.ini 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/tox.ini 2015-08-25 17:05:33.000000000 +0200 @@ -81,6 +81,8 @@ usedevelop = True deps = pbr + oslo.config + oslotest [testenv:py27-sa5-lxml-json] commands = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/exc.py new/WSME-0.8.0/wsme/exc.py --- old/WSME-0.7.0/wsme/exc.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/exc.py 2015-08-25 17:05:33.000000000 +0200 @@ -62,3 +62,31 @@ @property def faultstring(self): return _(six.u("Unknown function name: %s")) % (self.name) + + +class UnknownAttribute(ClientSideError): + def __init__(self, fieldname, attributes, msg=''): + self.fieldname = fieldname + self.attributes = attributes + self.msg = msg + super(UnknownAttribute, self).__init__(self.msg) + + @property + def faultstring(self): + error = _("Unknown attribute for argument %(argn)s: %(attrs)s") + if len(self.attributes) > 1: + error = _("Unknown attributes for argument %(argn)s: %(attrs)s") + str_attrs = ", ".join(self.attributes) + return error % {'argn': self.fieldname, 'attrs': str_attrs} + + def add_fieldname(self, name): + """Add a fieldname to concatenate the full name. + + Add a fieldname so that the whole hierarchy is displayed. Successive + calls to this method will prepend ``name`` to the hierarchy of names. + """ + if self.fieldname is not None: + self.fieldname = "{}.{}".format(name, self.fieldname) + else: + self.fieldname = name + super(UnknownAttribute, self).__init__(self.msg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/rest/args.py new/WSME-0.8.0/wsme/rest/args.py --- old/WSME-0.7.0/wsme/rest/args.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/rest/args.py 2015-08-25 17:05:33.000000000 +0200 @@ -181,8 +181,10 @@ except Exception: if isinstance(argdef.datatype, UserType): datatype_name = argdef.datatype.name - else: + elif isinstance(argdef.datatype, type): datatype_name = argdef.datatype.__name__ + else: + datatype_name = argdef.datatype.__class__.__name__ raise InvalidInput( argdef.name, arg, @@ -231,7 +233,8 @@ elif mimetype in restxml.accept_content_types: dataformat = restxml else: - raise ValueError("Unknown mimetype: %s" % mimetype) + raise ClientSideError("Unknown mimetype: %s" % mimetype, + status_code=415) try: kw = dataformat.parse( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/rest/json.py new/WSME-0.8.0/wsme/rest/json.py --- old/WSME-0.7.0/wsme/rest/json.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/rest/json.py 2015-08-25 17:05:33.000000000 +0200 @@ -1,6 +1,4 @@ -""" -REST+Json protocol implementation. -""" +"""REST+Json protocol implementation.""" from __future__ import absolute_import import datetime import decimal @@ -9,10 +7,10 @@ from simplegeneric import generic -from wsme.types import Unset +import wsme.exc import wsme.types +from wsme.types import Unset import wsme.utils -from wsme.exc import ClientSideError, UnknownArgument, InvalidInput try: @@ -116,8 +114,7 @@ @generic def fromjson(datatype, value): - """ - A generic converter from json base types to python datatype. + """A generic converter from json base types to python datatype. If a non-complex user specific type is to be used in the api, a specific fromjson should be added:: @@ -135,16 +132,31 @@ return None if wsme.types.iscomplex(datatype): obj = datatype() - for attrdef in wsme.types.list_attributes(datatype): + attributes = wsme.types.list_attributes(datatype) + + # Here we check that all the attributes in the value are also defined + # in our type definition, otherwise we raise an Error. + v_keys = set(value.keys()) + a_keys = set(adef.name for adef in attributes) + if not v_keys <= a_keys: + raise wsme.exc.UnknownAttribute(None, v_keys - a_keys) + + for attrdef in attributes: if attrdef.name in value: - val_fromjson = fromjson(attrdef.datatype, value[attrdef.name]) + try: + val_fromjson = fromjson(attrdef.datatype, + value[attrdef.name]) + except wsme.exc.UnknownAttribute as e: + e.add_fieldname(attrdef.name) + raise if getattr(attrdef, 'readonly', False): - raise InvalidInput(attrdef.name, val_fromjson, - "Cannot set read only field.") + raise wsme.exc.InvalidInput(attrdef.name, val_fromjson, + "Cannot set read only field.") setattr(obj, attrdef.key, val_fromjson) elif attrdef.mandatory: - raise InvalidInput(attrdef.name, None, - "Mandatory field missing.") + raise wsme.exc.InvalidInput(attrdef.name, None, + "Mandatory field missing.") + return wsme.types.validate_value(datatype, obj) elif wsme.types.isusertype(datatype): value = datatype.frombasetype( @@ -156,6 +168,8 @@ def array_fromjson(datatype, value): if value is None: return None + if not isinstance(value, list): + raise ValueError("Value not a valid list: %s" % value) return [fromjson(datatype.item_type, item) for item in value] @@ -163,6 +177,8 @@ def dict_fromjson(datatype, value): if value is None: return None + if not isinstance(value, dict): + raise ValueError("Value not a valid dict: %s" % value) return dict(( (fromjson(datatype.key_type, item[0]), fromjson(datatype.value_type, item[1])) @@ -243,16 +259,23 @@ try: jdata = jload(s) except ValueError: - raise ClientSideError("Request is not in valid JSON format") + raise wsme.exc.ClientSideError("Request is not in valid JSON format") if bodyarg: argname = list(datatypes.keys())[0] try: kw = {argname: fromjson(datatypes[argname], jdata)} except ValueError as e: - raise InvalidInput(argname, jdata, e.args[0]) + raise wsme.exc.InvalidInput(argname, jdata, e.args[0]) + except wsme.exc.UnknownAttribute as e: + # We only know the fieldname at this level, not in the + # called function. We fill in this information here. + e.add_fieldname(argname) + raise else: kw = {} extra_args = [] + if not isinstance(jdata, dict): + raise wsme.exc.ClientSideError("Request must be a JSON dict") for key in jdata: if key not in datatypes: extra_args.append(key) @@ -260,9 +283,14 @@ try: kw[key] = fromjson(datatypes[key], jdata[key]) except ValueError as e: - raise InvalidInput(key, jdata[key], e.args[0]) + raise wsme.exc.InvalidInput(key, jdata[key], e.args[0]) + except wsme.exc.UnknownAttribute as e: + # We only know the fieldname at this level, not in the + # called function. We fill in this information here. + e.add_fieldname(key) + raise if extra_args: - raise UnknownArgument(', '.join(extra_args)) + raise wsme.exc.UnknownArgument(', '.join(extra_args)) return kw diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/root.py new/WSME-0.8.0/wsme/root.py --- old/WSME-0.7.0/wsme/root.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/root.py 2015-08-25 17:05:33.000000000 +0200 @@ -203,6 +203,7 @@ infos = wsme.api.format_exception(sys.exc_info(), self._debug) if isinstance(e, ClientSideError): request.client_errorcount += 1 + request.client_last_status_code = e.code else: request.server_errorcount += 1 return protocol.encode_error(context, infos) @@ -266,6 +267,7 @@ request.calls = [] request.client_errorcount = 0 + request.client_last_status_code = None request.server_errorcount = 0 try: @@ -290,7 +292,9 @@ if hasattr(protocol, 'get_response_status'): res.status = protocol.get_response_status(request) else: - if request.client_errorcount: + if request.client_errorcount == 1: + res.status = request.client_last_status_code + elif request.client_errorcount: res.status = 400 elif request.server_errorcount: res.status = 500 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/tests/test_protocols_commons.py new/WSME-0.8.0/wsme/tests/test_protocols_commons.py --- old/WSME-0.7.0/wsme/tests/test_protocols_commons.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/tests/test_protocols_commons.py 2015-08-25 17:05:33.000000000 +0200 @@ -7,7 +7,11 @@ from wsme.rest.args import from_param, from_params, args_from_args from wsme.exc import InvalidInput -from wsme.types import UserType, Unset, ArrayType, DictType +from wsme.types import UserType, Unset, ArrayType, DictType, Base + + +class MyBaseType(Base): + test = str class MyUserType(UserType): @@ -89,6 +93,17 @@ else: self.fail('Should have thrown an InvalidInput') + def test_args_from_args_array_type(self): + fake_type = ArrayType(MyBaseType) + fd = FunctionDefinition(FunctionDefinition) + fd.arguments.append(FunctionArgument('fake-arg', fake_type, True, [])) + try: + args_from_args(fd, [['invalid-argument']], {}) + except InvalidInput as e: + assert ArrayType.__name__ in str(e) + else: + self.fail('Should have thrown an InvalidInput') + class ArgTypeConversion(unittest.TestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/WSME-0.7.0/wsme/tests/test_restjson.py new/WSME-0.8.0/wsme/tests/test_restjson.py --- old/WSME-0.7.0/wsme/tests/test_restjson.py 2015-06-01 15:19:37.000000000 +0200 +++ new/WSME-0.8.0/wsme/tests/test_restjson.py 2015-08-25 17:05:33.000000000 +0200 @@ -11,7 +11,8 @@ from wsme.rest.json import fromjson, tojson, parse from wsme.utils import parse_isodatetime, parse_isotime, parse_isodate -from wsme.types import isarray, isdict, isusertype, register_type, UserType +from wsme.types import isarray, isdict, isusertype, register_type +from wsme.types import UserType, ArrayType, DictType from wsme.rest import expose, validate from wsme.exc import ClientSideError, InvalidInput @@ -100,6 +101,10 @@ name = wsme.types.text +class NestedObj(wsme.types.Base): + o = Obj + + class CRUDResult(object): data = Obj message = wsme.types.text @@ -249,6 +254,15 @@ print(r) assert json.loads(r.text) == 2 + def test_invalid_content_type_body(self): + r = self.app.post('/argtypes/setint.json', '{"value": 2}', + headers={"Content-Type": "application/invalid"}, + expect_errors=True) + print(r) + assert r.status_int == 415 + assert json.loads(r.text)['faultstring'] == \ + "Unknown mimetype: application/invalid" + def test_invalid_json_body(self): r = self.app.post('/argtypes/setint.json', '{"value": 2', headers={"Content-Type": "application/json"}, @@ -325,6 +339,36 @@ j = parse('{"a": "2011-01-01"}', {'a': datetime.date}, False) assert isinstance(j['a'], datetime.date) + def test_invalid_root_dict_fromjson(self): + try: + parse('["invalid"]', {'a': ArrayType(str)}, False) + assert False + except Exception as e: + assert isinstance(e, ClientSideError) + assert e.msg == "Request must be a JSON dict" + + def test_invalid_list_fromjson(self): + jlist = "invalid" + try: + parse('{"a": "%s"}' % jlist, {'a': ArrayType(str)}, False) + assert False + except Exception as e: + assert isinstance(e, InvalidInput) + assert e.fieldname == 'a' + assert e.value == jlist + assert e.msg == "Value not a valid list: %s" % jlist + + def test_invalid_dict_fromjson(self): + jdict = "invalid" + try: + parse('{"a": "%s"}' % jdict, {'a': DictType(str, str)}, False) + assert False + except Exception as e: + assert isinstance(e, InvalidInput) + assert e.fieldname == 'a' + assert e.value == jdict + assert e.msg == "Value not a valid dict: %s" % jdict + def test_invalid_date_fromjson(self): jdate = "2015-01-invalid" try: @@ -476,6 +520,37 @@ "invalid literal for int() with base 10: '%s'" % value ) + def test_parse_unexpected_attribute(self): + o = { + "id": "1", + "name": "test", + "other": "unknown", + "other2": "still unknown", + } + for ba in True, False: + jd = o if ba else {"o": o} + try: + parse(json.dumps(jd), {'o': Obj}, ba) + raise AssertionError("Object should not parse correcty.") + except wsme.exc.UnknownAttribute as e: + self.assertEqual(e.attributes, set(['other', 'other2'])) + + def test_parse_unexpected_nested_attribute(self): + no = { + "o": { + "id": "1", + "name": "test", + "other": "unknown", + }, + } + for ba in False, True: + jd = no if ba else {"no": no} + try: + parse(json.dumps(jd), {'no': NestedObj}, ba) + except wsme.exc.UnknownAttribute as e: + self.assertEqual(e.attributes, set(['other'])) + self.assertEqual(e.fieldname, "no.o") + def test_nest_result(self): self.root.protocols[0].nest_result = True r = self.app.get('/returntypes/getint.json') @@ -659,6 +734,33 @@ assert result['data']['name'] == u("test") assert result['message'] == "read" + def test_unexpected_extra_arg(self): + headers = { + 'Content-Type': 'application/json', + } + data = {"id": 1, "name": "test"} + content = json.dumps({"data": data, "other": "unexpected"}) + res = self.app.put( + '/crud', + content, + headers=headers, + expect_errors=True) + self.assertEqual(res.status_int, 400) + + def test_unexpected_extra_attribute(self): + """Expect a failure if we send an unexpected object attribute.""" + headers = { + 'Content-Type': 'application/json', + } + data = {"id": 1, "name": "test", "other": "unexpected"} + content = json.dumps({"data": data}) + res = self.app.put( + '/crud', + content, + headers=headers, + expect_errors=True) + self.assertEqual(res.status_int, 400) + def test_body_arg(self): headers = { 'Content-Type': 'application/json',