Hello community, here is the log from the commit of package python-jsonpatch for openSUSE:Factory checked in at 2013-11-04 13:26:27 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jsonpatch (Old) and /work/SRC/openSUSE:Factory/.python-jsonpatch.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jsonpatch" Changes: -------- --- /work/SRC/openSUSE:Factory/python-jsonpatch/python-jsonpatch.changes 2013-07-22 13:48:20.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-jsonpatch.new/python-jsonpatch.changes 2013-11-04 13:26:28.000000000 +0100 @@ -1,0 +2,7 @@ +Mon Oct 21 13:18:40 UTC 2013 - dmuel...@suse.com + +- update to 1.3: + * Improved Tests, add command line utilities +- add update-alternatives + +------------------------------------------------------------------- Old: ---- jsonpatch-1.1.tar.gz New: ---- jsonpatch-1.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jsonpatch.spec ++++++ --- /var/tmp/diff_new_pack.Dt77Z6/_old 2013-11-04 13:26:30.000000000 +0100 +++ /var/tmp/diff_new_pack.Dt77Z6/_new 2013-11-04 13:26:30.000000000 +0100 @@ -17,9 +17,9 @@ Name: python-jsonpatch -Version: 1.1 +Version: 1.3 Release: 0 -Summary: Apply JSON-Patches (according to draft 08) +Summary: Python - JSON-Patches License: BSD-3-Clause Group: Development/Languages/Python Url: https://github.com/stefankoegl/python-json-patch @@ -27,6 +27,8 @@ BuildRequires: python-devel BuildRequires: python-jsonpointer Requires: python-jsonpointer >= 1.0 +Requires(post): update-alternatives +Requires(postun): update-alternatives BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} @@ -35,7 +37,7 @@ %endif %description -Apply JSON-Patches (according to draft 08) +Python module to apply JSON-Patches (according to RFC 6902). %prep %setup -q -n jsonpatch-%{version} @@ -45,9 +47,30 @@ %install python setup.py install --prefix=%{_prefix} --root=%{buildroot} +for i in patch diff; do + mv %{buildroot}%{_bindir}/json$i %{buildroot}%{_bindir}/json$i-%{py_ver} +done + +%pre +[[ ! -L %{_bindir}/jsonpatch ]] && rm -f %{_bindir}/jsonpatch || : +[[ ! -L %{_bindir}/jsondiff ]] && rm -f %{_bindir}/jsondiff || : + +%post +update-alternatives --install \ + %{_bindir}/jsonpatch jsonpatch %{_bindir}/jsonpatch-%{py_ver} 20 \ + --slave %{_bindir}/jsondiff jsondiff %{_bindir}/jsondiff-%{py_ver} + +%preun +if [ $1 -eq 0 ] ; then + update-alternatives --remove jsonpatch %{_bindir}/jsonpatch-%{py_ver} +fi %files %defattr(-,root,root,-) +%ghost %{_bindir}/jsonpatch +%{_bindir}/jsonpatch-%{py_ver} +%ghost %{_bindir}/jsondiff +%{_bindir}/jsondiff-%{py_ver} %{python_sitelib}/* %changelog ++++++ jsonpatch-1.1.tar.gz -> jsonpatch-1.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/AUTHORS new/jsonpatch-1.3/AUTHORS --- old/jsonpatch-1.1/AUTHORS 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/AUTHORS 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,4 @@ +Stefan Kögl <ste...@skoegl.net> +Alexander Shorin <kxe...@gmail.com> +Byron Ruth <br...@codeomics.com> +William Kral <william.k...@gmail.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/COPYING new/jsonpatch-1.3/COPYING --- old/jsonpatch-1.1/COPYING 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/COPYING 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,26 @@ +Copyright (c) 2011 Stefan Kögl <ste...@skoegl.net> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/MANIFEST.in new/jsonpatch-1.3/MANIFEST.in --- old/jsonpatch-1.1/MANIFEST.in 2012-12-20 19:06:35.000000000 +0100 +++ new/jsonpatch-1.3/MANIFEST.in 2013-10-13 15:07:25.000000000 +0200 @@ -1 +1,6 @@ include requirements.txt +include COPYING +include README.md +include tests.py +include ext_tests.py +include AUTHORS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/PKG-INFO new/jsonpatch-1.3/PKG-INFO --- old/jsonpatch-1.1/PKG-INFO 2013-06-27 21:50:38.000000000 +0200 +++ new/jsonpatch-1.3/PKG-INFO 2013-10-13 15:14:53.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: jsonpatch -Version: 1.1 +Version: 1.3 Summary: Apply JSON-Patches (RFC 6902) Home-page: https://github.com/stefankoegl/python-json-patch Author: Stefan Kögl diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/README.md new/jsonpatch-1.3/README.md --- old/jsonpatch-1.1/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/README.md 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,19 @@ +python-json-patch [![Build Status](https://secure.travis-ci.org/stefankoegl/python-json-patch.png?branch=master)](https://travis-ci.org/stefankoegl/python-json-patch) [![Coverage Status](https://coveralls.io/repos/stefankoegl/python-json-patch/badge.png?branch=master)](https://coveralls.io/r/stefankoegl/python-json-patch?branch=master) ![Downloads](https://pypip.in/d/jsonpatch/badge.png) ![Version](https://pypip.in/v/jsonpatch/badge.png) +================= +Applying JSON Patches in Python +------------------------------- + +Library to apply JSON Patches according to +[RFC 6902](http://tools.ietf.org/html/rfc6902) + +See Sourcecode for Examples + +* Website: https://github.com/stefankoegl/python-json-patch +* Repository: https://github.com/stefankoegl/python-json-patch.git +* Documentation: https://python-json-patch.readthedocs.org/ + +Running external tests +---------------------- +To run external tests (such as those from https://github.com/json-patch/json-patch-tests) use ext_test.py + + ./ext_tests.py ../json-patch-tests/tests.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/bin/jsondiff new/jsonpatch-1.3/bin/jsondiff --- old/jsonpatch-1.1/bin/jsondiff 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/bin/jsondiff 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import sys +import os.path +import json +import jsonpatch +import argparse + + +parser = argparse.ArgumentParser(description='Diff two JSON files') +parser.add_argument('FILE1', type=argparse.FileType('r')) +parser.add_argument('FILE2', type=argparse.FileType('r')) +parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') +parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpatch.__version__) + + +def main(): + try: + diff_files() + except KeyboardInterrupt: + sys.exit(1) + + +def diff_files(): + """ Diffs two JSON files and prints a patch """ + args = parser.parse_args() + doc1 = json.load(args.FILE1) + doc2 = json.load(args.FILE2) + patch = jsonpatch.make_patch(doc1, doc2) + print(json.dumps(patch.patch, indent=args.indent)) + + +if __name__ == "__main__": + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/bin/jsonpatch new/jsonpatch-1.3/bin/jsonpatch --- old/jsonpatch-1.1/bin/jsonpatch 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/bin/jsonpatch 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import sys +import os.path +import json +import jsonpatch +import argparse + + +parser = argparse.ArgumentParser( + description='Apply a JSON patch on a JSON file') +parser.add_argument('ORIGINAL', type=argparse.FileType('r'), + help='Original file') +parser.add_argument('PATCH', type=argparse.FileType('r'), + help='Patch file') +parser.add_argument('--indent', type=int, default=None, + help='Indent output by n spaces') +parser.add_argument('-v', '--version', action='version', + version='%(prog)s ' + jsonpatch.__version__) + + +def main(): + try: + patch_files() + except KeyboardInterrupt: + sys.exit(1) + + +def patch_files(): + """ Diffs two JSON files and prints a patch """ + args = parser.parse_args() + doc = json.load(args.ORIGINAL) + patch = json.load(args.PATCH) + result = jsonpatch.apply_patch(doc, patch) + print(json.dumps(result, indent=args.indent)) + + +if __name__ == "__main__": + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/ext_tests.py new/jsonpatch-1.3/ext_tests.py --- old/jsonpatch-1.1/ext_tests.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/ext_tests.py 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# python-json-patch - An implementation of the JSON Patch format +# https://github.com/stefankoegl/python-json-patch +# +# Copyright (c) 2011 Stefan Kögl <ste...@skoegl.net> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +""" Script to run external tests, eg from +https://github.com/json-patch/json-patch-tests """ + +from functools import partial +import doctest +import unittest +import jsonpatch +import sys + + +class TestCaseTemplate(unittest.TestCase): + """ A generic test case for running external tests """ + + def _test(self, test): + if not 'doc' in test or not 'patch' in test: + # incomplete + return + + if test.get('disabled', False): + # test is disabled + return + + if 'error' in test: + self.assertRaises( + (jsonpatch.JsonPatchException, jsonpatch.JsonPointerException), + jsonpatch.apply_patch, test['doc'], test['patch'] + ) + + else: + res = jsonpatch.apply_patch(test['doc'], test['patch']) + + # if there is no 'expected' we only verify that applying the patch + # does not raies an exception + if 'expected' in test: + self.assertEquals(res, test['expected']) + + +def make_test_case(tests): + + class MyTestCase(TestCaseTemplate): + pass + + for n, test in enumerate(tests): + add_test_method(MyTestCase, 'test_%d' % n, test) + + return MyTestCase + + +def add_test_method(cls, name, test): + setattr(cls, name, lambda self: self._test(test)) + + + +modules = ['jsonpatch'] +coverage_modules = [] + + +def get_suite(filenames): + suite = unittest.TestSuite() + + for testfile in filenames: + with open(testfile) as f: + # we use the (potentially) patched version of json.load here + tests = jsonpatch.json.load(f) + cls = make_test_case(tests) + suite.addTest(unittest.makeSuite(cls)) + + return suite + + +suite = get_suite(sys.argv[1:]) + +for module in modules: + m = __import__(module, fromlist=[module]) + coverage_modules.append(m) + suite.addTest(doctest.DocTestSuite(m)) + +runner = unittest.TextTestRunner(verbosity=1) + +try: + import coverage +except ImportError: + coverage = None + +if coverage is not None: + coverage.erase() + coverage.start() + +result = runner.run(suite) + +if not result.wasSuccessful(): + sys.exit(1) + +if coverage is not None: + coverage.stop() + coverage.report(coverage_modules) + coverage.erase() + +if coverage is None: + sys.stderr.write(""" +No coverage reporting done (Python module "coverage" is missing) +Please install the python-coverage package to get coverage reporting. +""") + sys.stderr.flush() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/jsonpatch.egg-info/PKG-INFO new/jsonpatch-1.3/jsonpatch.egg-info/PKG-INFO --- old/jsonpatch-1.1/jsonpatch.egg-info/PKG-INFO 2013-06-27 21:50:37.000000000 +0200 +++ new/jsonpatch-1.3/jsonpatch.egg-info/PKG-INFO 2013-10-13 15:14:53.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: jsonpatch -Version: 1.1 +Version: 1.3 Summary: Apply JSON-Patches (RFC 6902) Home-page: https://github.com/stefankoegl/python-json-patch Author: Stefan Kögl diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/jsonpatch.egg-info/SOURCES.txt new/jsonpatch-1.3/jsonpatch.egg-info/SOURCES.txt --- old/jsonpatch-1.1/jsonpatch.egg-info/SOURCES.txt 2013-06-27 21:50:38.000000000 +0200 +++ new/jsonpatch-1.3/jsonpatch.egg-info/SOURCES.txt 2013-10-13 15:14:53.000000000 +0200 @@ -1,7 +1,14 @@ +AUTHORS +COPYING MANIFEST.in +README.md +ext_tests.py jsonpatch.py requirements.txt setup.py +tests.py +bin/jsondiff +bin/jsonpatch jsonpatch.egg-info/PKG-INFO jsonpatch.egg-info/SOURCES.txt jsonpatch.egg-info/dependency_links.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/jsonpatch.py new/jsonpatch-1.3/jsonpatch.py --- old/jsonpatch-1.1/jsonpatch.py 2013-06-27 21:49:49.000000000 +0200 +++ new/jsonpatch-1.3/jsonpatch.py 2013-10-13 15:13:27.000000000 +0200 @@ -36,12 +36,14 @@ # Will be parsed by setup.py to determine package metadata __author__ = 'Stefan Kögl <ste...@skoegl.net>' -__version__ = '1.1' +__version__ = '1.3' __website__ = 'https://github.com/stefankoegl/python-json-patch' __license__ = 'Modified BSD License' import copy import sys +import operator +import collections import json @@ -69,6 +71,41 @@ """ A Test operation failed """ +def multidict(ordered_pairs): + """Convert duplicate keys values to lists.""" + # read all values into lists + d = collections.defaultdict(list) + for k, v in ordered_pairs: + d[k].append(v) + + # unpack lists that have only 1 item + for k, v in d.items(): + if len(v) == 1: + d[k] = v[0] + return dict(d) + + +def get_loadjson(): + """ adds the object_pairs_hook parameter to json.load when possible + + The "object_pairs_hook" parameter is used to handle duplicate keys when + loading a JSON object. This parameter does not exist in Python 2.6. This + methods returns an unmodified json.load for Python 2.6 and a partial + function with object_pairs_hook set to multidict for Python versions that + support the parameter. """ + + import inspect + import functools + + argspec = inspect.getargspec(json.load) + if 'object_pairs_hook' not in argspec.args: + return json.load + + return functools.partial(json.load, object_pairs_hook=multidict) + +json.load = get_loadjson() + + def apply_patch(doc, patch, in_place=False): """Apply list of patches to specified json document. @@ -195,14 +232,15 @@ def __hash__(self): - return hash(tuple(self._get_operation(op) for op in self.patch)) + return hash(tuple(self._ops)) def __eq__(self, other): if not isinstance(other, JsonPatch): return False - return self.patch == other.patch + return len(list(self._ops)) == len(list(other._ops)) and \ + all(map(operator.eq, self._ops, other._ops)) @classmethod @@ -282,6 +320,10 @@ """Returns patch set as JSON string.""" return json.dumps(self.patch) + @property + def _ops(self): + return map(self._get_operation, self.patch) + def apply(self, obj, in_place=False): """Applies the patch to given object. @@ -298,8 +340,7 @@ if not in_place: obj = copy.deepcopy(obj) - for operation in self.patch: - operation = self._get_operation(operation) + for operation in self._ops: obj = operation.apply(obj) return obj @@ -309,6 +350,10 @@ raise JsonPatchException("Operation does not contain 'op' member") op = operation['op'] + + if not isinstance(op, basestring): + raise JsonPatchException("Operation must be a string") + if op not in self.operations: raise JsonPatchException("Unknown operation '%s'" % op) @@ -348,7 +393,7 @@ subobj, part = self.pointer.to_last(obj) try: del subobj[part] - except KeyError as ex: + except IndexError as ex: raise JsonPatchConflict(str(ex)) return obj @@ -359,7 +404,12 @@ def apply(self, obj): value = self.operation["value"] - subobj, part = self.pointer.to_last(obj, None) + subobj, part = self.pointer.to_last(obj) + + # type is already checked in to_last(), so we assert here + # for consistency + assert isinstance(subobj, list) or isinstance(subobj, dict), \ + "invalid document type %s" (type(doc),) if isinstance(subobj, list): @@ -380,10 +430,6 @@ else: subobj[part] = value - else: - raise JsonPatchConflict("can't add to type '%s'" - "" % subobj.__class__.__name__) - return obj @@ -394,6 +440,11 @@ value = self.operation["value"] subobj, part = self.pointer.to_last(obj) + # type is already checked in to_last(), so we assert here + # for consistency + assert isinstance(subobj, list) or isinstance(subobj, dict), \ + "invalid document type %s" (type(doc),) + if part is None: return value @@ -406,10 +457,6 @@ raise JsonPatchConflict("can't replace non-existant object '%s'" "" % part) - else: - raise JsonPatchConflict("can't replace in type '%s'" - "" % subobj.__class__.__name__) - subobj[part] = value return obj @@ -422,7 +469,7 @@ subobj, part = from_ptr.to_last(obj) value = subobj[part] - if from_ptr.contains(self.pointer): + if self.pointer.contains(from_ptr): raise JsonPatchException('Cannot move values into its own children') obj = RemoveOperation({'op': 'remove', 'path': self.operation['from']}).apply(obj) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/setup.py new/jsonpatch-1.3/setup.py --- old/jsonpatch-1.1/setup.py 2013-03-26 12:54:25.000000000 +0100 +++ new/jsonpatch-1.3/setup.py 2013-10-13 15:06:14.000000000 +0200 @@ -52,5 +52,11 @@ url=WEBSITE, py_modules=MODULES, package_data={'': ['requirements.txt']}, + scripts=['bin/jsondiff', 'bin/jsonpatch'], + entry_poimts = { + 'console_scripts': [ + 'jsondiff = jsondiff:main', + 'jsonpatch = jsonpatch:main', + ]}, **OPTIONS ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpatch-1.1/tests.py new/jsonpatch-1.3/tests.py --- old/jsonpatch-1.1/tests.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpatch-1.3/tests.py 2013-10-13 15:06:14.000000000 +0200 @@ -0,0 +1,358 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import json +import doctest +import unittest +import jsonpatch +import jsonpointer +import sys + + +class ApplyPatchTestCase(unittest.TestCase): + + def test_apply_patch_from_string(self): + obj = {'foo': 'bar'} + patch = '[{"op": "add", "path": "/baz", "value": "qux"}]' + res = jsonpatch.apply_patch(obj, patch) + self.assertTrue(obj is not res) + self.assertTrue('baz' in res) + self.assertEqual(res['baz'], 'qux') + + def test_apply_patch_to_copy(self): + obj = {'foo': 'bar'} + res = jsonpatch.apply_patch(obj, [{'op': 'add', 'path': '/baz', 'value': 'qux'}]) + self.assertTrue(obj is not res) + + def test_apply_patch_to_same_instance(self): + obj = {'foo': 'bar'} + res = jsonpatch.apply_patch(obj, [{'op': 'add', 'path': '/baz', 'value': 'qux'}], + in_place=True) + self.assertTrue(obj is res) + + def test_add_object_key(self): + obj = {'foo': 'bar'} + res = jsonpatch.apply_patch(obj, [{'op': 'add', 'path': '/baz', 'value': 'qux'}]) + self.assertTrue('baz' in res) + self.assertEqual(res['baz'], 'qux') + + def test_add_array_item(self): + obj = {'foo': ['bar', 'baz']} + res = jsonpatch.apply_patch(obj, [{'op': 'add', 'path': '/foo/1', 'value': 'qux'}]) + self.assertEqual(res['foo'], ['bar', 'qux', 'baz']) + + def test_remove_object_key(self): + obj = {'foo': 'bar', 'baz': 'qux'} + res = jsonpatch.apply_patch(obj, [{'op': 'remove', 'path': '/baz'}]) + self.assertTrue('baz' not in res) + + def test_remove_array_item(self): + obj = {'foo': ['bar', 'qux', 'baz']} + res = jsonpatch.apply_patch(obj, [{'op': 'remove', 'path': '/foo/1'}]) + self.assertEqual(res['foo'], ['bar', 'baz']) + + def test_replace_object_key(self): + obj = {'foo': 'bar', 'baz': 'qux'} + res = jsonpatch.apply_patch(obj, [{'op': 'replace', 'path': '/baz', 'value': 'boo'}]) + self.assertTrue(res['baz'], 'boo') + + def test_replace_whole_document(self): + obj = {'foo': 'bar'} + res = jsonpatch.apply_patch(obj, [{'op': 'replace', 'path': '', 'value': {'baz': 'qux'}}]) + self.assertTrue(res['baz'], 'qux') + + def test_add_replace_whole_document(self): + obj = {'foo': 'bar'} + new_obj = {'baz': 'qux'} + res = jsonpatch.apply_patch(obj, [{'op': 'add', 'path': '', 'value': new_obj}]) + self.assertTrue(res, new_obj) + + def test_replace_array_item(self): + obj = {'foo': ['bar', 'qux', 'baz']} + res = jsonpatch.apply_patch(obj, [{'op': 'replace', 'path': '/foo/1', + 'value': 'boo'}]) + self.assertEqual(res['foo'], ['bar', 'boo', 'baz']) + + def test_move_object_key(self): + obj = {'foo': {'bar': 'baz', 'waldo': 'fred'}, + 'qux': {'corge': 'grault'}} + res = jsonpatch.apply_patch(obj, [{'op': 'move', 'from': '/foo/waldo', + 'path': '/qux/thud'}]) + self.assertEqual(res, {'qux': {'thud': 'fred', 'corge': 'grault'}, + 'foo': {'bar': 'baz'}}) + + def test_move_array_item(self): + obj = {'foo': ['all', 'grass', 'cows', 'eat']} + res = jsonpatch.apply_patch(obj, [{'op': 'move', 'from': '/foo/1', 'path': '/foo/3'}]) + self.assertEqual(res, {'foo': ['all', 'cows', 'eat', 'grass']}) + + def test_copy_object_key(self): + obj = {'foo': {'bar': 'baz', 'waldo': 'fred'}, + 'qux': {'corge': 'grault'}} + res = jsonpatch.apply_patch(obj, [{'op': 'copy', 'from': '/foo/waldo', + 'path': '/qux/thud'}]) + self.assertEqual(res, {'qux': {'thud': 'fred', 'corge': 'grault'}, + 'foo': {'bar': 'baz', 'waldo': 'fred'}}) + + def test_copy_array_item(self): + obj = {'foo': ['all', 'grass', 'cows', 'eat']} + res = jsonpatch.apply_patch(obj, [{'op': 'copy', 'from': '/foo/1', 'path': '/foo/3'}]) + self.assertEqual(res, {'foo': ['all', 'grass', 'cows', 'grass', 'eat']}) + + + def test_copy_mutable(self): + """ test if mutable objects (dicts and lists) are copied by value """ + obj = {'foo': [{'bar': 42}, {'baz': 3.14}], 'boo': []} + # copy object somewhere + res = jsonpatch.apply_patch(obj, [{'op': 'copy', 'from': '/foo/0', 'path': '/boo/0' }]) + self.assertEqual(res, {'foo': [{'bar': 42}, {'baz': 3.14}], 'boo': [{'bar': 42}]}) + # modify original object + res = jsonpatch.apply_patch(res, [{'op': 'add', 'path': '/foo/0/zoo', 'value': 255}]) + # check if that didn't modify the copied object + self.assertEqual(res['boo'], [{'bar': 42}]) + + + def test_test_success(self): + obj = {'baz': 'qux', 'foo': ['a', 2, 'c']} + jsonpatch.apply_patch(obj, [{'op': 'test', 'path': '/baz', 'value': 'qux'}, + {'op': 'test', 'path': '/foo/1', 'value': 2}]) + + def test_test_whole_obj(self): + obj = {'baz': 1} + jsonpatch.apply_patch(obj, [{'op': 'test', 'path': '', 'value': obj}]) + + + def test_test_error(self): + obj = {'bar': 'qux'} + self.assertRaises(jsonpatch.JsonPatchTestFailed, + jsonpatch.apply_patch, + obj, [{'op': 'test', 'path': '/bar', 'value': 'bar'}]) + + + def test_test_not_existing(self): + obj = {'bar': 'qux'} + self.assertRaises(jsonpatch.JsonPatchTestFailed, + jsonpatch.apply_patch, + obj, [{'op': 'test', 'path': '/baz', 'value': 'bar'}]) + + + def test_test_noval_existing(self): + obj = {'bar': 'qux'} + jsonpatch.apply_patch(obj, [{'op': 'test', 'path': '/bar'}]) + + + def test_test_noval_not_existing(self): + obj = {'bar': 'qux'} + self.assertRaises(jsonpatch.JsonPatchTestFailed, + jsonpatch.apply_patch, + obj, [{'op': 'test', 'path': '/baz'}]) + + + def test_test_noval_not_existing_nested(self): + obj = {'bar': {'qux': 2}} + self.assertRaises(jsonpatch.JsonPatchTestFailed, + jsonpatch.apply_patch, + obj, [{'op': 'test', 'path': '/baz/qx'}]) + + + def test_unrecognized_element(self): + obj = {'foo': 'bar', 'baz': 'qux'} + res = jsonpatch.apply_patch(obj, [{'op': 'replace', 'path': '/baz', 'value': 'boo', 'foo': 'ignore'}]) + self.assertTrue(res['baz'], 'boo') + + + def test_append(self): + obj = {'foo': [1, 2]} + res = jsonpatch.apply_patch(obj, [ + {'op': 'add', 'path': '/foo/-', 'value': 3}, + {'op': 'add', 'path': '/foo/-', 'value': 4}, + ]) + self.assertEqual(res['foo'], [1, 2, 3, 4]) + + + +class EqualityTestCase(unittest.TestCase): + + def test_patch_equality(self): + patch1 = jsonpatch.JsonPatch([{ "op": "add", "path": "/a/b/c", "value": "foo" }]) + patch2 = jsonpatch.JsonPatch([{ "path": "/a/b/c", "op": "add", "value": "foo" }]) + self.assertEqual(patch1, patch2) + + + def test_patch_unequal(self): + patch1 = jsonpatch.JsonPatch([{'op': 'test', 'path': '/test'}]) + patch2 = jsonpatch.JsonPatch([{'op': 'test', 'path': '/test1'}]) + self.assertNotEqual(patch1, patch2) + + def test_patch_hash_equality(self): + patch1 = jsonpatch.JsonPatch([{ "op": "add", "path": "/a/b/c", "value": "foo" }]) + patch2 = jsonpatch.JsonPatch([{ "path": "/a/b/c", "op": "add", "value": "foo" }]) + self.assertEqual(hash(patch1), hash(patch2)) + + + def test_patch_hash_unequal(self): + patch1 = jsonpatch.JsonPatch([{'op': 'test', 'path': '/test'}]) + patch2 = jsonpatch.JsonPatch([{'op': 'test', 'path': '/test1'}]) + self.assertNotEqual(hash(patch1), hash(patch2)) + + + def test_patch_neq_other_objs(self): + p = [{'op': 'test', 'path': '/test'}] + patch = jsonpatch.JsonPatch(p) + # a patch will always compare not-equal to objects of other types + self.assertFalse(patch == p) + self.assertFalse(patch == None) + + # also a patch operation will always compare + # not-equal to objects of other types + op = jsonpatch.PatchOperation(p[0]) + self.assertFalse(op == p[0]) + self.assertFalse(op == None) + + def test_str(self): + patch_obj = [ { "op": "add", "path": "/child", "value": { "grandchild": { } } } ] + patch = jsonpatch.JsonPatch(patch_obj) + + self.assertEqual(json.dumps(patch_obj), str(patch)) + self.assertEqual(json.dumps(patch_obj), patch.to_string()) + + + +class MakePatchTestCase(unittest.TestCase): + + def test_apply_patch_to_copy(self): + src = {'foo': 'bar', 'boo': 'qux'} + dst = {'baz': 'qux', 'foo': 'boo'} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src) + self.assertTrue(src is not res) + + def test_apply_patch_to_same_instance(self): + src = {'foo': 'bar', 'boo': 'qux'} + dst = {'baz': 'qux', 'foo': 'boo'} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src, in_place=True) + self.assertTrue(src is res) + + def test_objects(self): + src = {'foo': 'bar', 'boo': 'qux'} + dst = {'baz': 'qux', 'foo': 'boo'} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src) + self.assertEqual(res, dst) + + def test_arrays(self): + src = {'numbers': [1, 2, 3], 'other': [1, 3, 4, 5]} + dst = {'numbers': [1, 3, 4, 5], 'other': [1, 3, 4]} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src) + self.assertEqual(res, dst) + + def test_complex_object(self): + src = {'data': [ + {'foo': 1}, {'bar': [1, 2, 3]}, {'baz': {'1': 1, '2': 2}} + ]} + dst = {'data': [ + {'foo': [42]}, {'bar': []}, {'baz': {'boo': 'oom!'}} + ]} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src) + self.assertEqual(res, dst) + + def test_array_add_remove(self): + # see https://github.com/stefankoegl/python-json-patch/issues/4 + src = {'numbers': [], 'other': [1, 5, 3, 4]} + dst = {'numbers': [1, 3, 4, 5], 'other': []} + patch = jsonpatch.make_patch(src, dst) + res = patch.apply(src) + self.assertEqual(res, dst) + + def test_add_nested(self): + # see http://tools.ietf.org/html/draft-ietf-appsawg-json-patch-03#appendix-A.10 + src = {"foo": "bar"} + patch_obj = [ { "op": "add", "path": "/child", "value": { "grandchild": { } } } ] + res = jsonpatch.apply_patch(src, patch_obj) + expected = { "foo": "bar", + "child": { "grandchild": { } } + } + self.assertEqual(expected, res) + + +class InvalidInputTests(unittest.TestCase): + + def test_missing_op(self): + # an "op" member is required + src = {"foo": "bar"} + patch_obj = [ { "path": "/child", "value": { "grandchild": { } } } ] + self.assertRaises(jsonpatch.JsonPatchException, jsonpatch.apply_patch, src, patch_obj) + + + def test_invalid_op(self): + # "invalid" is not a valid operation + src = {"foo": "bar"} + patch_obj = [ { "op": "invalid", "path": "/child", "value": { "grandchild": { } } } ] + self.assertRaises(jsonpatch.JsonPatchException, jsonpatch.apply_patch, src, patch_obj) + + +class ConflictTests(unittest.TestCase): + + def test_remove_indexerror(self): + src = {"foo": [1, 2]} + patch_obj = [ { "op": "remove", "path": "/foo/10"} ] + self.assertRaises(jsonpatch.JsonPatchConflict, jsonpatch.apply_patch, src, patch_obj) + + def test_remove_keyerror(self): + src = {"foo": [1, 2]} + patch_obj = [ { "op": "remove", "path": "/foo/b"} ] + self.assertRaises(jsonpointer.JsonPointerException, jsonpatch.apply_patch, src, patch_obj) + + def test_insert_oob(self): + src = {"foo": [1, 2]} + patch_obj = [ { "op": "add", "path": "/foo/10", "value": 1} ] + self.assertRaises(jsonpatch.JsonPatchConflict, jsonpatch.apply_patch, src, patch_obj) + + def test_move_into_child(self): + src = {"foo": {"bar": {"baz": 1}}} + patch_obj = [ { "op": "move", "from": "/foo", "path": "/foo/bar" } ] + self.assertRaises(jsonpatch.JsonPatchException, jsonpatch.apply_patch, src, patch_obj) + + def test_replace_oob(self): + src = {"foo": [1, 2]} + patch_obj = [ { "op": "replace", "path": "/foo/10", "value": 10} ] + self.assertRaises(jsonpatch.JsonPatchConflict, jsonpatch.apply_patch, src, patch_obj) + + def test_replace_missing(self): + src = {"foo": 1} + patch_obj = [ { "op": "replace", "path": "/bar", "value": 10} ] + self.assertRaises(jsonpatch.JsonPatchConflict, jsonpatch.apply_patch, src, patch_obj) + + + +modules = ['jsonpatch'] + + +def get_suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(jsonpatch)) + suite.addTest(unittest.makeSuite(ApplyPatchTestCase)) + suite.addTest(unittest.makeSuite(EqualityTestCase)) + suite.addTest(unittest.makeSuite(MakePatchTestCase)) + suite.addTest(unittest.makeSuite(InvalidInputTests)) + suite.addTest(unittest.makeSuite(ConflictTests)) + return suite + + +suite = get_suite() + +for module in modules: + m = __import__(module, fromlist=[module]) + suite.addTest(doctest.DocTestSuite(m)) + +runner = unittest.TextTestRunner(verbosity=1) + +result = runner.run(suite) + +if not result.wasSuccessful(): + sys.exit(1) -- To unsubscribe, e-mail: opensuse-commit+unsubscr...@opensuse.org For additional commands, e-mail: opensuse-commit+h...@opensuse.org