11"""JSON Patch, as per RFC 6902."""
2+
23from __future__ import annotations
34
45import copy
@@ -85,16 +86,16 @@ def asdict(self) -> Dict[str, object]:
8586 return {"op" : self .name , "path" : str (self .path ), "value" : self .value }
8687
8788
88- class OpAddNe (Op ):
89- """A non-standard _add if not exists_ operation."""
89+ class OpAddNe (OpAdd ):
90+ """A non-standard _add if not exists_ operation.
9091
91- __slots__ = ("path" , "value" )
92+ This is like _OpAdd_, but only adds object/dict keys/values if they key does
93+ not already exist.
94+ """
9295
93- name = "add"
96+ __slots__ = ( "path" , "value" )
9497
95- def __init__ (self , path : JSONPointer , value : object ) -> None :
96- self .path = path
97- self .value = value
98+ name = "addne"
9899
99100 def apply (
100101 self , data : Union [MutableSequence [object ], MutableMapping [str , object ]]
@@ -115,20 +116,45 @@ def apply(
115116 raise JSONPatchError ("index out of range" )
116117 else :
117118 parent .insert (int (target ), self .value )
118- elif (
119- isinstance (parent , MutableMapping )
120- and parent .get (target , UNDEFINED ) == UNDEFINED
121- ):
119+ elif isinstance (parent , MutableMapping ) and target not in parent :
122120 parent [target ] = self .value
123- # else:
124- # raise JSONPatchError(
125- # f"unexpected operation on {parent.__class__.__name__!r}"
126- # )
127121 return data
128122
129- def asdict (self ) -> Dict [str , object ]:
130- """Return a dictionary representation of this operation."""
131- return {"op" : self .name , "path" : str (self .path ), "value" : self .value }
123+
124+ class OpAddAp (OpAdd ):
125+ """A non-standard add operation that appends to arrays/lists .
126+
127+ This is like _OpAdd_, but assumes an index of "-" if the path can not
128+ be resolved.
129+ """
130+
131+ __slots__ = ("path" , "value" )
132+
133+ name = "addap"
134+
135+ def apply (
136+ self , data : Union [MutableSequence [object ], MutableMapping [str , object ]]
137+ ) -> Union [MutableSequence [object ], MutableMapping [str , object ]]:
138+ """Apply this patch operation to _data_."""
139+ parent , obj = self .path .resolve_parent (data )
140+ if parent is None :
141+ # Replace the root object.
142+ # The following op, if any, will raise a JSONPatchError if needed.
143+ return self .value # type: ignore
144+
145+ target = self .path .parts [- 1 ]
146+ if isinstance (parent , MutableSequence ):
147+ if obj is UNDEFINED :
148+ parent .append (self .value )
149+ else :
150+ parent .insert (int (target ), self .value )
151+ elif isinstance (parent , MutableMapping ):
152+ parent [target ] = self .value
153+ else :
154+ raise JSONPatchError (
155+ f"unexpected operation on { parent .__class__ .__name__ !r} "
156+ )
157+ return data
132158
133159
134160class OpRemove (Op ):
@@ -388,8 +414,13 @@ def _build(self, patch: Iterable[Mapping[str, object]]) -> None:
388414 )
389415 elif op == "addne" :
390416 self .addne (
391- path = self ._op_pointer (operation , "path" , "add" , i ),
392- value = self ._op_value (operation , "value" , "add" , i ),
417+ path = self ._op_pointer (operation , "path" , "addne" , i ),
418+ value = self ._op_value (operation , "value" , "addne" , i ),
419+ )
420+ elif op == "addap" :
421+ self .addne (
422+ path = self ._op_pointer (operation , "path" , "addap" , i ),
423+ value = self ._op_value (operation , "value" , "addap" , i ),
393424 )
394425 elif op == "remove" :
395426 self .remove (path = self ._op_pointer (operation , "path" , "add" , i ))
@@ -491,6 +522,22 @@ def addne(self: Self, path: Union[str, JSONPointer], value: object) -> Self:
491522 self .ops .append (OpAddNe (path = pointer , value = value ))
492523 return self
493524
525+ def addap (self : Self , path : Union [str , JSONPointer ], value : object ) -> Self :
526+ """Append an _addap_ operation to this patch.
527+
528+ Arguments:
529+ path: A string representation of a JSON Pointer, or one that has
530+ already been parsed.
531+ value: The object to add.
532+
533+ Returns:
534+ This `JSONPatch` instance, so we can build a JSON Patch by chaining
535+ calls to JSON Patch operation methods.
536+ """
537+ pointer = self ._ensure_pointer (path )
538+ self .ops .append (OpAddAp (path = pointer , value = value ))
539+ return self
540+
494541 def remove (self : Self , path : Union [str , JSONPointer ]) -> Self :
495542 """Append a _remove_ operation to this patch.
496543
0 commit comments