Skip to content

[pull] master from stefankoegl:master #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 167 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
167 commits
Select commit Hold shift + click to select a range
70306fa
Fix build failure on Travis CI with Python 3.2
selurvedu Oct 18, 2015
a2ba02f
Merge pull request #45 from selurvedu/py32_coverage
stefankoegl Oct 22, 2015
0ae80ba
Allow running certain tests separately
selurvedu Aug 14, 2015
99bf616
Merge pull request #43 from selurvedu/separate-tests
stefankoegl Oct 28, 2015
3f2328e
Fix bug in _split_by_common_seq using wrong range in right subtree
Nov 3, 2015
2a02d21
Add failing test for #40
stefankoegl Nov 3, 2015
46ad04c
Merge pull request #46 from apinkney97/master, fixes #40
stefankoegl Nov 3, 2015
d877f1d
bump version to 1.12
stefankoegl Nov 3, 2015
5c2a9b9
Add encoding info to setup.py
stefankoegl Feb 13, 2016
32dcbb0
Create "universal" wheel packages
stefankoegl Feb 13, 2016
a33021b
Optimize "deep" ``replace`` operation, fixes #36
stefankoegl Feb 13, 2016
4443d32
Make ``move`` operation with from == path a no-op
stefankoegl Feb 13, 2016
cf0da04
Print test comments when external tests fail
stefankoegl Feb 13, 2016
9ce8487
Remove import of ``pudb``
stefankoegl Feb 13, 2016
b15d8f1
bump version to 1.13
stefankoegl Feb 13, 2016
5cc9bee
Update trove classification to include Python 3.5
stefankoegl Feb 13, 2016
282beba
Extend tests that check list patching
selurvedu Aug 14, 2015
c106735
Allow longer patches in test_use_move_...
selurvedu Aug 19, 2015
6761340
added error-prone cases to default tests
tyerq Aug 18, 2015
0734d45
Update tests from ea80865 to run on Python 3.2
selurvedu Oct 18, 2015
73acf7f
Move list-related testcases into separate class
selurvedu Apr 8, 2016
a2cd6da
Merge branch 'list_tests@v1.9' into list_tests@v1.13
selurvedu Apr 8, 2016
72a90e3
Fix KeyError in add/remove optimization
go1dshtein May 4, 2016
c9ac1d9
Merge pull request #50 from go1dshtein/master
stefankoegl May 31, 2016
d6e9a00
Use inspect.signature() on Python 3
vstinner Jun 29, 2016
55e0c43
Merge pull request #52 from haypo/signature
stefankoegl Jul 22, 2016
4e95310
If there is no diff print nothing; exit 1 if a diff is found (#53)
gergnz Oct 2, 2016
1dbc03c
Run tests with Python 3.6
Dec 30, 2016
a01bec7
Update trove classification to include Python 3.6
stefankoegl Jan 12, 2017
8b46602
bump version to 1.14
stefankoegl Jan 12, 2017
27f1f98
bump version to 1.15
stefankoegl Jan 12, 2017
d1f317a
Fix: optimization bugs #55, #54, add more tests
mrhadenough Mar 8, 2017
05d9ace
Fix: python 3.2 tests
mrhadenough Mar 8, 2017
e18a131
don't apply patch optimization when it's incorrect
mrhadenough Apr 27, 2017
18887df
Merge branch 'mrhadenough-fix_optimization_bugs'
stefankoegl Jun 15, 2017
1fc5e20
Bump version to 1.16
stefankoegl Jun 15, 2017
e8fbd18
Avoid double work (#62)
mithrandi Jul 9, 2017
4d9adf1
Merge commit '1fc5e2022ff2c5796bd28af56646b135ede4ee71'
stefankoegl Jul 17, 2017
b878d85
fixing array diff bug (issue #30)
velislavgerov Jul 26, 2017
845cf4a
added a test case for issue #30
velislavgerov Jul 26, 2017
717b0db
Merge branch 'list_tests@v1.13' into list_tests
stefankoegl Sep 3, 2017
fbc904f
Disable tests for disabled optimizations
stefankoegl Sep 3, 2017
f0a4f51
Merge branch 'list_tests'
stefankoegl Sep 3, 2017
7583258
Merge pull request #65 from thunderstruck47/master
stefankoegl Sep 10, 2017
e649871
Remove trailing whitespace
stefankoegl Sep 10, 2017
d778745
Remove support for Python 3.2
stefankoegl Sep 10, 2017
1964607
Drop in patch creation from jsondiff
stefankoegl Sep 10, 2017
82ac779
Remove re-creation of no-optimization patch
stefankoegl Sep 10, 2017
2f45e50
Re-enable previously disabled optimization tests
stefankoegl Sep 10, 2017
03aa14e
Merge _op_base classes into PatchOperation classes
stefankoegl Sep 10, 2017
73daf9b
Break long lines
stefankoegl Sep 10, 2017
098c7c7
Redefine _compare_info class as DiffBuilder
stefankoegl Sep 10, 2017
7387d20
Simplify compatibility code
stefankoegl Sep 10, 2017
2d9a565
Rename old{path,key} to from_{path,key}
stefankoegl Sep 10, 2017
462c9cb
Code style
stefankoegl Sep 10, 2017
3cec8a0
Improve optimizations
stefankoegl Sep 10, 2017
d602f5e
Fix unicode dict keys in Python 2
stefankoegl Sep 10, 2017
4351804
Remove support for Python 3.2
stefankoegl Sep 10, 2017
7079bdc
Remove support for Python 2.6
stefankoegl Oct 21, 2017
b6514dd
Update supported versions in docs
stefankoegl Oct 21, 2017
074f937
Avoid overriding json.load (fixes #37)
stefankoegl Oct 21, 2017
6b777b7
Show coverage report after tests
stefankoegl Oct 29, 2017
7b4fb66
Merge branch 'master' into jsondiff
stefankoegl Nov 25, 2017
7465c1b
Merge branch 'jsonload-no-override'
stefankoegl Nov 25, 2017
66c2e50
Merge branch 'jsondiff'
stefankoegl Nov 25, 2017
ae15a3b
Reformat .travis.yml
stefankoegl Nov 25, 2017
5ea6224
Deploy to PyPI from Travis-CI
stefankoegl Nov 25, 2017
e066c28
Disable tests for Python 2.6
stefankoegl Nov 25, 2017
d8c54d6
Bump version to 1.20
stefankoegl Nov 25, 2017
04596b8
Add test case for issue reported in #74
stefankoegl Nov 25, 2017
ae895f7
Add test case for issue reported in #41
stefankoegl Nov 25, 2017
62db640
Update ext_tests.py for current versions of coverage
stefankoegl Nov 25, 2017
df0c56d
Remove broken badges from README
stefankoegl Nov 25, 2017
aae6082
Expect path/from attributes also as JsonPoiner instances (#60)
stefankoegl Dec 3, 2017
e500b4d
Remove extraneous 'value' field for op:remove (#76)
ostackbrian Dec 4, 2017
cfd69e5
Merge pull request #77 from ostackbrian/issue-76
stefankoegl Dec 30, 2017
4ff7398
Bump version to 1.21
stefankoegl Dec 30, 2017
71bdeed
Drop support for EOL Python 3.3
hugovk Jan 16, 2018
d5a7aed
Remove ununsed imports and variables
hugovk Jan 16, 2018
02fa1f8
Update badges and fix typos
hugovk Jan 16, 2018
0642254
Merge pull request #81 from hugovk/rm-3.3
stefankoegl Jan 16, 2018
7b664c4
Add tests.js to MANIFEST.in, fixes #82
stefankoegl Mar 11, 2018
7e55ad6
Bump version to 1.22
stefankoegl Apr 2, 2018
3c621da
Add project URLs to setup.py
stefankoegl Apr 2, 2018
0c96a53
Merge pull request #75 from stefankoegl/jsonptr
stefankoegl Apr 2, 2018
36b245a
Bump version to 1.23
stefankoegl Apr 2, 2018
4c6f547
better exception when unable to fully resolve a jsonpointer on add or…
guillp Jul 17, 2018
2990bb3
Merge pull request #89 from guillp/patch-1
stefankoegl Aug 1, 2018
0410363
Ensure an item is within the upper array boundaries before replacing it
May 16, 2019
1b83d63
Fix move for numeric dictionary keys (issue #97)
Jun 28, 2019
b5141ab
Merge tag 'v1.23'
Jul 22, 2019
53817c0
bump version
Jul 22, 2019
33e630e
Merge pull request #99 from itkach/master
stefankoegl Jul 28, 2019
abf27f2
Support Python 3.7, test with Python 3.8 dev
stefankoegl Jul 28, 2019
911b79f
Merge pull request #100 from stefankoegl/python-3.8
stefankoegl Jul 28, 2019
2f2790b
Travis CI: Add Python 3.8 production release
cclauss Nov 9, 2019
9ca2e21
fixup! setup.py: Add Python 3.8 and remove 3.4
cclauss Nov 9, 2019
3e44e04
Merge pull request #104 from cclauss/patch-1
stefankoegl Nov 13, 2019
ed43114
Test to reproduce:
Jan 28, 2020
028089d
Flagging type-only changes with DiffBuilder
Jan 28, 2020
97e18f3
Changing previous type comparison to just comparing how json.dumps re…
Jan 28, 2020
b3726f3
Merge pull request #96 from gdraynz/replace-array-indexerror
stefankoegl Jan 28, 2020
c1fce71
Added comment.
Jan 28, 2020
91f6124
Merge pull request #106 from blakehilliard/only-type-diff
stefankoegl Jan 29, 2020
e99d178
Make it possible for from_diff to support custom types (issue #107)
clj Jan 31, 2020
8fbed9b
Fixed some typos and wording
vavanade Feb 27, 2020
9e4d423
Merge pull request #109 from vavanade/patch-1
stefankoegl Mar 2, 2020
0167d34
Subclassing can override json dumper and loader
clj Mar 6, 2020
1457aaf
Merge branch 'master' into custom-types
clj Mar 6, 2020
29c989e
Make DiffBuilder's dumps argument optional
clj Mar 16, 2020
1015d7f
fix #111: optimizing exception message
Alanscut May 23, 2020
86f82be
fix #102: optimize error handling
Alanscut Jun 9, 2020
625555e
Merge pull request #113 from Alanscut/issue-102
stefankoegl Jun 22, 2020
4fe5c2c
Merge pull request #112 from Alanscut/issue-111
stefankoegl Jun 22, 2020
ab775d1
feat: add custom json pointer support
Nov 12, 2020
bb4ea7b
test: custo json pointer
Nov 12, 2020
124eb76
doc: fix docstrings
Nov 12, 2020
fb04fcc
test: add more tests
Nov 15, 2020
0b680ea
chore: bump version
Nov 16, 2020
c37b40f
test: update
Nov 16, 2020
0994bfe
test: add toy jsonpointer example
Nov 17, 2020
d24fa96
test: fix for py27
Nov 17, 2020
4d07392
style: fix typo
Nov 17, 2020
c9613e3
chore: revert version bump
Nov 17, 2020
bfc0f5a
Update coveragerc and require coverage.
dave-shawley Nov 17, 2020
a7ef7e8
fix #110: Validate patch documents during creation.
dave-shawley Nov 17, 2020
b44e7a2
Add tests for operation doc structure.
dave-shawley Nov 17, 2020
50fb942
tests: moar
Nov 17, 2020
511cbc2
Merge pull request #108 from paperlessreceipts/custom-types
stefankoegl Nov 20, 2020
3bb3351
Explain the call to _get_operation in __init__.
dave-shawley Nov 20, 2020
24b5e86
Merge pull request #116 from dave-shawley/validate-patch-doc
stefankoegl Nov 20, 2020
eca4f8a
Merge branch 'master' into feature/custom-pointer
Nov 23, 2020
3a95635
Merge pull request #114 from tzoiker/feature/custom-pointer
stefankoegl Nov 23, 2020
3972a8e
Update Python to 3.9
stefankoegl Nov 23, 2020
f3c46e8
Merge pull request #117 from stefankoegl/py3.9
stefankoegl Nov 23, 2020
b8083d7
feat: make operations class-based
Nov 23, 2020
1268e09
test: custom operations
Nov 23, 2020
9310d48
test: fix
Nov 24, 2020
57e4273
Merge pull request #118 from tzoiker/fix/json-patch-ops
stefankoegl Dec 1, 2020
a9a83b5
Bump version to 1.28
stefankoegl Dec 1, 2020
8d15ed5
Fix make_patch
RyanSept Feb 1, 2021
78abec1
Add comment
RyanSept Feb 18, 2021
f6b26b2
Update comment
RyanSept Feb 18, 2021
25762af
Merge pull request #122 from RyanSept/119-120-fix-diffbuilder
stefankoegl Mar 2, 2021
dbea3db
Fix version number v1.30
stefankoegl Mar 2, 2021
974d54f
Add support for preserving Unicode characters in jsonpatch CLI
Genzer Mar 3, 2021
cc37e25
Merge pull request #127 from Genzer/master
stefankoegl Mar 4, 2021
7a6d76a
Remove failing pypy build
stefankoegl Mar 4, 2021
d1cfec3
Bump version to 1.31
stefankoegl Mar 4, 2021
5cdb066
closes #129
Penguin2600 Mar 12, 2021
ce15b23
Merge pull request #130 from Penguin2600/master
stefankoegl Mar 13, 2021
fd9fa23
Merge branch 'master' of github.com:stefankoegl/python-json-patch
stefankoegl Mar 13, 2021
55d4816
Bump version to 1.32
stefankoegl Mar 13, 2021
c9bfb91
FIX: TypeError when one forgot to put its operation in a list.
JulienPalard Mar 16, 2021
a652648
Merge pull request #132 from JulienPalard/mdk/TypeError
stefankoegl Mar 29, 2021
db194f8
fix invalid remove index
Apr 6, 2021
46eef55
remove unused import
Apr 6, 2021
e0b3a9b
Merge pull request #134 from Ventilateur/b/fix-invalid-remove-index
stefankoegl Apr 12, 2021
714df3c
docs: fix simple typo, raies -> raise (#135)
timgates42 Sep 14, 2021
a76f742
feat(jsondiff): Add support for preserving Unicode characters (#145)
hirmiura Jun 16, 2023
33562b0
Update license text to match official 3-clause-BSD (#142)
skypanther Jun 16, 2023
45cfe90
Switch to GitHub actions (#144)
stefankoegl Jun 16, 2023
0b05203
bump version to 1.33
stefankoegl Jun 16, 2023
e5a007a
add .readthedocs.yaml
stefankoegl Jun 16, 2023
73c36f2
Update documentation to include a link to the GitHub repo and install…
werdnum Jun 28, 2023
a22e05a
chore: add Python 3.10-3.12 as supported versions (#156)
hf-kklein Feb 24, 2024
d8e1a6e
Fix tests for Python 3.12 (#162)
CyrilRoelandteNovance Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# .coveragerc to control coverage.py
[run]
branch = True
source = jsonpatch

[report]
show_missing = True
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
Expand Down
52 changes: 52 additions & 0 deletions doc/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,55 @@ explicitly.
# or from a list
>>> patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
>>> res = jsonpatch.apply_patch(obj, patch)


Dealing with Custom Types
-------------------------

Custom JSON dump and load functions can be used to support custom types such as
`decimal.Decimal`. The following examples shows how the
`simplejson <https://simplejson.readthedocs.io/>`_ package, which has native
support for Python's ``Decimal`` type, can be used to create a custom
``JsonPatch`` subclass with ``Decimal`` support:

.. code-block:: python

>>> import decimal
>>> import simplejson

>>> class DecimalJsonPatch(jsonpatch.JsonPatch):
@staticmethod
def json_dumper(obj):
return simplejson.dumps(obj)

@staticmethod
def json_loader(obj):
return simplejson.loads(obj, use_decimal=True,
object_pairs_hook=jsonpatch.multidict)

>>> src = {}
>>> dst = {'bar': decimal.Decimal('1.10')}
>>> patch = DecimalJsonPatch.from_diff(src, dst)
>>> doc = {'foo': 1}
>>> result = patch.apply(doc)
{'foo': 1, 'bar': Decimal('1.10')}

Instead of subclassing it is also possible to pass a dump function to
``from_diff``:

>>> patch = jsonpatch.JsonPatch.from_diff(src, dst, dumps=simplejson.dumps)

a dumps function to ``to_string``:

>>> serialized_patch = patch.to_string(dumps=simplejson.dumps)
'[{"op": "add", "path": "/bar", "value": 1.10}]'

and load function to ``from_string``:

>>> import functools
>>> loads = functools.partial(simplejson.loads, use_decimal=True,
object_pairs_hook=jsonpatch.multidict)
>>> patch.from_string(serialized_patch, loads=loads)
>>> doc = {'foo': 1}
>>> result = patch.apply(doc)
{'foo': 1, 'bar': Decimal('1.10')}
44 changes: 34 additions & 10 deletions jsonpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ def make_patch(src, dst, pointer_cls=JsonPointer):


class JsonPatch(object):
json_dumper = staticmethod(json.dumps)
json_loader = staticmethod(_jsonloads)

"""A JSON Patch is a list of Patch Operations.

>>> patch = JsonPatch([
Expand Down Expand Up @@ -229,6 +232,13 @@ def __init__(self, patch, pointer_cls=JsonPointer):
'copy': CopyOperation,
}

# Verify that the structure of the patch document
# is correct by retrieving each patch element.
# Much of the validation is done in the initializer
# though some is delayed until the patch is applied.
for op in self.patch:
self._get_operation(op)

def __str__(self):
"""str(self) -> self.to_string()"""
return self.to_string()
Expand All @@ -253,22 +263,30 @@ def __ne__(self, other):
return not(self == other)

@classmethod
def from_string(cls, patch_str, pointer_cls=JsonPointer):
def from_string(cls, patch_str, loads=None, pointer_cls=JsonPointer):
"""Creates JsonPatch instance from string source.

:param patch_str: JSON patch as raw string.
:type pointer_cls: str
:type patch_str: str

:param loads: A function of one argument that loads a serialized
JSON string.
:type loads: function

:param pointer_cls: JSON pointer class to use.
:type pointer_cls: Type[JsonPointer]

:return: :class:`JsonPatch` instance.
"""
patch = _jsonloads(patch_str)
json_loader = loads or cls.json_loader
patch = json_loader(patch_str)
return cls(patch, pointer_cls=pointer_cls)

@classmethod
def from_diff(cls, src, dst, optimization=True, pointer_cls=JsonPointer):
def from_diff(
cls, src, dst, optimization=True, dumps=None,
pointer_cls=JsonPointer,
):
"""Creates JsonPatch instance based on comparison of two document
objects. Json patch would be created for `src` argument against `dst`
one.
Expand All @@ -279,6 +297,10 @@ def from_diff(cls, src, dst, optimization=True, pointer_cls=JsonPointer):
:param dst: Data source document object.
:type dst: dict

:param dumps: A function of one argument that produces a serialized
JSON string.
:type dumps: function

:param pointer_cls: JSON pointer class to use.
:type pointer_cls: Type[JsonPointer]

Expand All @@ -291,15 +313,16 @@ def from_diff(cls, src, dst, optimization=True, pointer_cls=JsonPointer):
>>> new == dst
True
"""

builder = DiffBuilder(pointer_cls=pointer_cls)
json_dumper = dumps or cls.json_dumper
builder = DiffBuilder(json_dumper, pointer_cls=pointer_cls)
builder._compare_values('', None, src, dst)
ops = list(builder.execute())
return cls(ops, pointer_cls=pointer_cls)

def to_string(self):
def to_string(self, dumps=None):
"""Returns patch set as JSON string."""
return json.dumps(self.patch)
json_dumper = dumps or self.json_dumper
return json_dumper(self.patch)

@property
def _ops(self):
Expand Down Expand Up @@ -660,7 +683,8 @@ def apply(self, obj):

class DiffBuilder(object):

def __init__(self, pointer_cls=JsonPointer):
def __init__(self, dumps=json.dumps, pointer_cls=JsonPointer):
self.dumps = dumps
self.pointer_cls = pointer_cls
self.index_storage = [{}, {}]
self.index_storage2 = [[], []]
Expand Down Expand Up @@ -856,7 +880,7 @@ def _compare_values(self, path, key, src, dst):
# and ignore those that don't. The performance of this could be
# improved by doing more direct type checks, but we'd need to be
# careful to accept type changes that don't matter when JSONified.
elif json.dumps(src) == json.dumps(dst):
elif self.dumps(src) == self.dumps(dst):
return

else:
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage
wheel
pypandoc
142 changes: 141 additions & 1 deletion tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import unicode_literals

import json
import decimal
import doctest
import unittest
import jsonpatch
Expand Down Expand Up @@ -278,6 +279,34 @@ def test_str(self):
self.assertEqual(json.dumps(patch_obj), patch.to_string())


def custom_types_dumps(obj):
def default(obj):
if isinstance(obj, decimal.Decimal):
return {'__decimal__': str(obj)}
raise TypeError('Unknown type')

return json.dumps(obj, default=default)


def custom_types_loads(obj):
def as_decimal(dct):
if '__decimal__' in dct:
return decimal.Decimal(dct['__decimal__'])
return dct

return json.loads(obj, object_hook=as_decimal)


class CustomTypesJsonPatch(jsonpatch.JsonPatch):
@staticmethod
def json_dumper(obj):
return custom_types_dumps(obj)

@staticmethod
def json_loader(obj):
return custom_types_loads(obj)


class MakePatchTestCase(unittest.TestCase):

def test_apply_patch_to_copy(self):
Expand Down Expand Up @@ -456,6 +485,35 @@ def test_issue103(self):
self.assertEqual(res, dst)
self.assertIsInstance(res['A'], float)

def test_custom_types_diff(self):
old = {'value': decimal.Decimal('1.0')}
new = {'value': decimal.Decimal('1.00')}
generated_patch = jsonpatch.JsonPatch.from_diff(
old, new, dumps=custom_types_dumps)
str_patch = generated_patch.to_string(dumps=custom_types_dumps)
loaded_patch = jsonpatch.JsonPatch.from_string(
str_patch, loads=custom_types_loads)
self.assertEqual(generated_patch, loaded_patch)
new_from_patch = jsonpatch.apply_patch(old, generated_patch)
self.assertEqual(new, new_from_patch)

def test_custom_types_subclass(self):
old = {'value': decimal.Decimal('1.0')}
new = {'value': decimal.Decimal('1.00')}
generated_patch = CustomTypesJsonPatch.from_diff(old, new)
str_patch = generated_patch.to_string()
loaded_patch = CustomTypesJsonPatch.from_string(str_patch)
self.assertEqual(generated_patch, loaded_patch)
new_from_patch = jsonpatch.apply_patch(old, loaded_patch)
self.assertEqual(new, new_from_patch)

def test_custom_types_subclass_load(self):
old = {'value': decimal.Decimal('1.0')}
new = {'value': decimal.Decimal('1.00')}
patch = CustomTypesJsonPatch.from_string(
'[{"op": "replace", "path": "/value", "value": {"__decimal__": "1.00"}}]')
new_from_patch = jsonpatch.apply_patch(old, patch)
self.assertEqual(new, new_from_patch)


class OptimizationTests(unittest.TestCase):
Expand Down Expand Up @@ -671,6 +729,86 @@ def test_create_with_pointer(self):
self.assertEqual(result, expected)


class JsonPatchCreationTest(unittest.TestCase):

def test_creation_fails_with_invalid_patch(self):
invalid_patches = [
{ 'path': '/foo', 'value': 'bar'},
{'op': 0xADD, 'path': '/foo', 'value': 'bar'},
{'op': 'boo', 'path': '/foo', 'value': 'bar'},
{'op': 'add', 'value': 'bar'},
]
for patch in invalid_patches:
with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.JsonPatch([patch])

with self.assertRaises(jsonpointer.JsonPointerException):
jsonpatch.JsonPatch([{'op': 'add', 'path': 'foo', 'value': 'bar'}])


class UtilityMethodTests(unittest.TestCase):

def test_boolean_coercion(self):
empty_patch = jsonpatch.JsonPatch([])
self.assertFalse(empty_patch)

def test_patch_equality(self):
p = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'bar'}])
q = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'bar'}])
different_op = jsonpatch.JsonPatch([{'op': 'remove', 'path': '/foo'}])
different_path = jsonpatch.JsonPatch([{'op': 'add', 'path': '/bar', 'value': 'bar'}])
different_value = jsonpatch.JsonPatch([{'op': 'add', 'path': '/foo', 'value': 'foo'}])
self.assertNotEqual(p, different_op)
self.assertNotEqual(p, different_path)
self.assertNotEqual(p, different_value)
self.assertEqual(p, q)

def test_operation_equality(self):
add = jsonpatch.AddOperation({'path': '/new-element', 'value': 'new-value'})
add2 = jsonpatch.AddOperation({'path': '/new-element', 'value': 'new-value'})
rm = jsonpatch.RemoveOperation({'path': '/target'})
self.assertEqual(add, add2)
self.assertNotEqual(add, rm)

def test_add_operation_structure(self):
with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.AddOperation({'path': '/'}).apply({})

def test_replace_operation_structure(self):
with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.ReplaceOperation({'path': '/'}).apply({})

with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.ReplaceOperation({'path': '/top/-', 'value': 'foo'}).apply({'top': {'inner': 'value'}})

with self.assertRaises(jsonpatch.JsonPatchConflict):
jsonpatch.ReplaceOperation({'path': '/top/missing', 'value': 'foo'}).apply({'top': {'inner': 'value'}})

def test_move_operation_structure(self):
with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.MoveOperation({'path': '/target'}).apply({})

with self.assertRaises(jsonpatch.JsonPatchConflict):
jsonpatch.MoveOperation({'from': '/source', 'path': '/target'}).apply({})

def test_test_operation_structure(self):
with self.assertRaises(jsonpatch.JsonPatchTestFailed):
jsonpatch.TestOperation({'path': '/target'}).apply({})

with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.TestOperation({'path': '/target'}).apply({'target': 'value'})

def test_copy_operation_structure(self):
with self.assertRaises(jsonpatch.InvalidJsonPatch):
jsonpatch.CopyOperation({'path': '/target'}).apply({})

with self.assertRaises(jsonpatch.JsonPatchConflict):
jsonpatch.CopyOperation({'path': '/target', 'from': '/source'}).apply({})

with self.assertRaises(jsonpatch.JsonPatchConflict):
jsonpatch.CopyOperation({'path': '/target', 'from': '/source'}).apply({})


class CustomJsonPointer(jsonpointer.JsonPointer):
pass

Expand All @@ -690,7 +828,7 @@ def test_json_patch_from_string(self):
self.assertEqual(res.pointer_cls, CustomJsonPointer)

def test_json_patch_from_object(self):
patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}],
patch = [{'op': 'add', 'path': '/baz', 'value': 'qux'}]
res = jsonpatch.JsonPatch(
patch, pointer_cls=CustomJsonPointer,
)
Expand Down Expand Up @@ -815,6 +953,8 @@ def get_suite():
suite.addTest(unittest.makeSuite(ConflictTests))
suite.addTest(unittest.makeSuite(OptimizationTests))
suite.addTest(unittest.makeSuite(JsonPointerTests))
suite.addTest(unittest.makeSuite(JsonPatchCreationTest))
suite.addTest(unittest.makeSuite(UtilityMethodTests))
suite.addTest(unittest.makeSuite(CustomJsonPointerTests))
return suite

Expand Down