Patch application
Methods JSONPatch.apply(data) and JSONPatch.atomic(data) - and their function equivalents patch.apply(ops, data) and patch.atomic(ops, data) - are mutating operations. They modify their data argument in-place.
apply(data) updates data directly. In the event of a failed patch operation, all preceding patch operations are still applied to data and a JSONPatchError exception is raised. apply is the lowest level form of JSONPatch application.
atomic(data) is defined in terms of apply. It applies patch operations to a deep copy of data, and only mutates data after all patch operations succeed.
def atomic(self, data: dict[str, Any] | list[Any]) -> dict[str, Any] | list[Any]:
data_ = copy.deepcopy(data)
self.apply(data_) # This could raise a JSONPatchError.
data.clear()
if isinstance(data, dict):
data.update(data_)
else:
data.extend(data_)
return data
Both apply and atomic return mutated data too. That is important for apply in the case of a patch operation replacing the document root.
Using atomic to apply patch operations that replace the document root leads to undefined behaviour. (This is a separate issue. We should probably raise an exception.)
Non-mutating patch application
We are considering adding a third, non-mutating form of patch application, also defined in terms of apply.
def patch(self, data: dict[str, Any] | list[Any]) -> dict[str, Any] | list[Any]:
return self.apply(copy.deepcopy(data))
Data passed to patch is never mutated, so failed patch operations cannot leave partial changes visible to the caller. You either get a new object with all patch ops applied, or an exception is raised. The same behaviour can be achieved with an explicit deep copy and the existing apply method/function.
patch = JSONPatch([
{ "op": "replace", "path": "/a/b/c", "value": 43 },
{ "op": "test", "path": "/a/b/c", "value": 43 }
])
data = {'a': {'b': {'c': 1}}}
patched_data = patch.apply(copy.deepcopy(data))
assert data == {'a': {'b': {'c': 1}}}
assert patched_data == {'a': {'b': {'c': 43}}}
That is, atomic(data) mutates data, but doesn't partially apply. patch(data) doesn't mutate data, it always returns a new patched object.
Objections to the name patch
patch is not an ideal name for our non-mutating method and module-level function:
patch does not immediately convey the fact that a potentially expensive deep copy of data occurs.
- We end up with "stuttering" when qualifying
patch - patch.patch(ops, data) and JSONPatch.patch(data).
An alternative name under consideration is apply_copy.
Patch application
Methods
JSONPatch.apply(data)andJSONPatch.atomic(data)- and their function equivalentspatch.apply(ops, data)andpatch.atomic(ops, data)- are mutating operations. They modify theirdataargument in-place.apply(data)updatesdatadirectly. In the event of a failed patch operation, all preceding patch operations are still applied todataand aJSONPatchErrorexception is raised.applyis the lowest level form of JSONPatch application.atomic(data)is defined in terms ofapply. It applies patch operations to a deep copy ofdata, and only mutatesdataafter all patch operations succeed.Both
applyandatomicreturn mutated data too. That is important forapplyin the case of a patch operation replacing the document root.Using
atomicto apply patch operations that replace the document root leads to undefined behaviour. (This is a separate issue. We should probably raise an exception.)Non-mutating patch application
We are considering adding a third, non-mutating form of patch application, also defined in terms of
apply.Data passed to
patchis never mutated, so failed patch operations cannot leave partial changes visible to the caller. You either get a new object with all patch ops applied, or an exception is raised. The same behaviour can be achieved with an explicit deep copy and the existingapplymethod/function.That is,
atomic(data)mutatesdata, but doesn't partially apply.patch(data)doesn't mutate data, it always returns a new patched object.Objections to the name
patchpatchis not an ideal name for our non-mutating method and module-level function:patchdoes not immediately convey the fact that a potentially expensive deep copy ofdataoccurs.patch-patch.patch(ops, data)andJSONPatch.patch(data).An alternative name under consideration is
apply_copy.