NTV.json_ntv.ntv_patch
Created on Sept 10 2023
@author: Philippe@loco-labs.io
The ntv_patch
module is part of the NTV.json_ntv
package (specification document).
It contains the classes NtvOp
, NtvPatch
.
1 - NTV Patch
NTV Patch is a transposition of JSON Patch defined in RFC6902.
NTV Patch is a format for expressing a sequence of operations to be applied to a target NTV entity.
This format is also potentially useful in cases where it is necessary to make partial updates on an NTV entity.
The representation of an NTV Patch is a JSON-Array that can be added to an NTV entity (e.g. comments and change management).
2 - Example
[
{'op': 'add', 'path': '/0/liste/0', 'entity': {'new value': 51}}
{'op': 'test', 'path': '/0/1/-', 'entity': {'new value': 51}}
{'op': 'remove', 'path': '/0/1/-'}
]
1# -*- coding: utf-8 -*- 2""" 3Created on Sept 10 2023 4 5@author: Philippe@loco-labs.io 6 7The `ntv_patch` module is part of the `NTV.json_ntv` package ([specification document]( 8https://loco-philippe.github.io/ES/JSON%20semantic%20format%20(JSON-NTV).htm)). 9 10It contains the classes `NtvOp`, `NtvPatch`. 11 12# 1 - NTV Patch 13 14NTV Patch is a transposition of JSON Patch defined in RFC6902. 15 16NTV Patch is a format for expressing a sequence of operations to be applied to a 17target NTV entity. 18 19This format is also potentially useful in cases where it is necessary to 20make partial updates on an NTV entity. 21 22The representation of an NTV Patch is a JSON-Array that can be added to an NTV 23entity (e.g. comments and change management). 24 25# 2 - Example 26 27``` 28 [ 29 {'op': 'add', 'path': '/0/liste/0', 'entity': {'new value': 51}} 30 {'op': 'test', 'path': '/0/1/-', 'entity': {'new value': 51}} 31 {'op': 'remove', 'path': '/0/1/-'} 32 ] 33``` 34""" 35import json 36from copy import copy 37 38OPERATIONS = ['add', 'test', 'move', 'remove', 'copy', 'replace'] 39 40class NtvOp: 41 ''' The NtvOp class defines operations to apply to an NTV entity''' 42 43 def __init__(self, op, path=None, entity=None, comment=None, from_path=None): 44 op = op.json if isinstance(op, NtvOp) else op 45 dic = isinstance(op, dict) 46 self.op = op.get('op') if dic else op 47 self.entity = op.get('entity') if dic else entity 48 self.comment = op.get('comment') if dic else comment 49 self.from_path = NtvPointer(op.get('from')) if dic else NtvPointer(from_path) 50 self.path = NtvPointer(op.get('path')) if dic else NtvPointer(path) 51 if not self.path or not self.op in OPERATIONS: 52 raise NtvOpError('path or op is not correct') 53 54 def __repr__(self): 55 '''return the op and the path''' 56 return 'op : ' + (self.op + ',').ljust(8, ' ') + ' path : ' + str(self.path) 57 58 def __str__(self): 59 '''return json format''' 60 return json.dumps(self.json) 61 62 def __eq__(self, other): 63 ''' equal if op, path, entity, comment and from_path are equal''' 64 return self.__class__.__name__ == other.__class__.__name__ and\ 65 self.op == other.op and self.path == other.path and\ 66 self.entity == other.entity and self.comment == other.comment and\ 67 self.from_path == other.from_path 68 69 @property 70 def json(self): 71 '''return the json-value representation (dict)''' 72 dic = {'op': self.op, 'path': str(self.path), 'entity': self.entity, 73 'comment':self.comment, 'from': str(self.from_path)} 74 return {key: val for key, val in dic.items() if val} 75 76 def exe(self, ntv): 77 '''execute the operation with ntv entity and return the resulting entity''' 78 from json_ntv.ntv import Ntv 79 ntv_res = copy(ntv) 80 idx = self.path[-1] 81 p_path = str(NtvPointer(self.path[:-1])) 82 path = str(self.path) 83 if self.op in ['move', 'copy', 'add']: 84 if self.op == 'add' and self.entity: 85 ntv = Ntv.obj(self.entity) 86 elif self.op == 'copy' and self.from_path: 87 ntv = copy(ntv_res[str(self.from_path)]) 88 elif self.op == 'move' and self.from_path: 89 ntv = ntv_res[str(self.from_path)] 90 del ntv_res[str(NtvPointer(self.from_path[:-1]))][self.from_path[-1]] 91 ntv.parent = None 92 else: 93 raise NtvOpError('op is not correct') 94 if idx == '-': 95 ntv_res[p_path].append(ntv) 96 else: 97 ntv_res[p_path].insert(idx, ntv) 98 elif self.op == 'test' and self.entity: 99 ntv = Ntv.obj(self.entity) 100 if not (idx == '-' and ntv in ntv_res[p_path]) and not ( 101 isinstance(idx, int) and ntv == ntv_res[path]): 102 raise NtvOpError('test is not correct') 103 elif self.op == 'remove': 104 idx = self.path[-1] 105 idx = len(ntv[p_path]) - 1 if idx == '-' else idx 106 ntv_res[p_path+'/'+str(idx)].remove(index=idx) 107 elif self.op == 'replace' and self.entity: 108 ntv_res[path].replace(Ntv.obj(self.entity)) 109 else: 110 raise NtvOpError('op add no result') 111 return ntv_res 112 113class NtvPatch: 114 ''' The NtvPatch class defines a sequence of operations to apply to an 115 NTV entity''' 116 117 def __init__(self, list_op=None): 118 list_op = [] if not list_op else list_op 119 self.list_op = [NtvOp(ope) for ope in list_op] 120 121 def __eq__(self, other): 122 ''' equal if list_op are equal''' 123 return self.__class__.__name__ == other.__class__.__name__ and\ 124 self.list_op == other.list_op 125 126 def __copy__(self): 127 ''' Copy all the data ''' 128 cop = self.__class__(self) 129 return cop 130 131 def __setitem__(self, ind, ope): 132 ''' replace op item at the `ind` row with `op`''' 133 if ind < 0 or ind >= len(self): 134 raise NtvOpError("out of bounds") 135 self.list_op[ind] = ope 136 137 def __delitem__(self, ind): 138 '''remove ntv_value item at the `ind` row''' 139 if isinstance(ind, int): 140 self.list_op.pop(ind) 141 else: 142 self.list_op.pop(self.list_op.index(self[ind])) 143 144 def __len__(self): 145 ''' len of list_op''' 146 return len(self.list_op) 147 148 def __str__(self): 149 '''return list of op json format''' 150 return json.dumps([ope.json for ope in self.list_op]) 151 152 def __repr__(self): 153 '''return classname and code''' 154 rep = 'NtvPatch :\n' 155 for ind, op in enumerate(self): 156 rep += ' op' + str(ind).ljust(3, ' ') + ' : ' + repr(op)[5:] + '\n' 157 return rep 158 159 def __contains__(self, item): 160 ''' item of NtvPatch''' 161 return item in self.list_op 162 163 def __iter__(self): 164 ''' iterator for op''' 165 return iter(self.list_op) 166 167 def __getitem__(self, selec): 168 ''' return ntv_value item ''' 169 if selec is None or selec == [] or selec == () or selec == '': 170 return self 171 if isinstance(selec, (list, tuple)) and len(selec) == 1: 172 selec = selec[0] 173 if isinstance(selec, (list, tuple)): 174 return [self[i] for i in selec] 175 return self.list_op[selec] 176 177 def append(self, ope): 178 '''append ope in the NtvPatch''' 179 self.list_op.append(ope) 180 181 def exe(self, ntv): 182 '''execute the included operations with ntv entity and return 183 the resulting entity''' 184 ntv_res = ntv 185 for ope in self: 186 ntv_res = ope.exe(ntv_res) 187 return ntv_res 188 189class NtvPointer(list): 190 191 def __init__(self, pointer): 192 if isinstance(pointer, (list, NtvPointer)): 193 super().__init__(pointer) 194 elif isinstance(pointer, (int, str)): 195 super().__init__(NtvPointer.pointer_list(pointer)) 196 197 def __str__(self): 198 return self.json() 199 200 def json(self, default=''): 201 '''convert a pointer into a json_pointer 202 203 *Parameters* 204 205 - **default**: Str (default '') - default value if pointer is empty 206 ''' 207 return NtvPointer.pointer_json(self) 208 209 def append(self, child): 210 '''append a child pointer into a pointer ''' 211 self += NtvPointer(child) 212 213 @staticmethod 214 def split(path): 215 '''return the last pointer of the path and the path without the last pointer''' 216 pointer = NtvPointer(path) 217 if pointer == []: 218 return (None, None) 219 return (NtvPointer(pointer[-1]), NtvPointer(pointer[:-1])) 220 221 @staticmethod 222 def pointer_json(list_pointer, default=''): 223 '''convert a list of pointer string into a json_pointer 224 225 *Parameters* 226 227 - **default**: Str (default '') - default value if pointer is empty 228 ''' 229 json_p = '' 230 if list_pointer == []: 231 return default 232 for name in list_pointer: 233 json_p += '/' + str(name).replace('~', '~0').replace('/', '~1') 234 return json_p 235 236 @staticmethod 237 def pointer_list(json_pointer): 238 '''convert a json_pointer string into a pointer list''' 239 json_pointer = str(json_pointer) 240 split_pointer = json_pointer.split('/') 241 if len(split_pointer) == 0: 242 return [] 243 if split_pointer[0] != '' and len(split_pointer) > 1: 244 raise NtvOpError("json_pointer is not correct") 245 if split_pointer[0] != '': 246 split_pointer.insert(0, '') 247 return [int(nam) if nam.isdigit() else nam.replace('~1', '/').replace('~0', '/') 248 for nam in split_pointer[1:] ] 249 250class NtvOpError(Exception): 251 ''' NtvOp Exception''' 252 # pass
41class NtvOp: 42 ''' The NtvOp class defines operations to apply to an NTV entity''' 43 44 def __init__(self, op, path=None, entity=None, comment=None, from_path=None): 45 op = op.json if isinstance(op, NtvOp) else op 46 dic = isinstance(op, dict) 47 self.op = op.get('op') if dic else op 48 self.entity = op.get('entity') if dic else entity 49 self.comment = op.get('comment') if dic else comment 50 self.from_path = NtvPointer(op.get('from')) if dic else NtvPointer(from_path) 51 self.path = NtvPointer(op.get('path')) if dic else NtvPointer(path) 52 if not self.path or not self.op in OPERATIONS: 53 raise NtvOpError('path or op is not correct') 54 55 def __repr__(self): 56 '''return the op and the path''' 57 return 'op : ' + (self.op + ',').ljust(8, ' ') + ' path : ' + str(self.path) 58 59 def __str__(self): 60 '''return json format''' 61 return json.dumps(self.json) 62 63 def __eq__(self, other): 64 ''' equal if op, path, entity, comment and from_path are equal''' 65 return self.__class__.__name__ == other.__class__.__name__ and\ 66 self.op == other.op and self.path == other.path and\ 67 self.entity == other.entity and self.comment == other.comment and\ 68 self.from_path == other.from_path 69 70 @property 71 def json(self): 72 '''return the json-value representation (dict)''' 73 dic = {'op': self.op, 'path': str(self.path), 'entity': self.entity, 74 'comment':self.comment, 'from': str(self.from_path)} 75 return {key: val for key, val in dic.items() if val} 76 77 def exe(self, ntv): 78 '''execute the operation with ntv entity and return the resulting entity''' 79 from json_ntv.ntv import Ntv 80 ntv_res = copy(ntv) 81 idx = self.path[-1] 82 p_path = str(NtvPointer(self.path[:-1])) 83 path = str(self.path) 84 if self.op in ['move', 'copy', 'add']: 85 if self.op == 'add' and self.entity: 86 ntv = Ntv.obj(self.entity) 87 elif self.op == 'copy' and self.from_path: 88 ntv = copy(ntv_res[str(self.from_path)]) 89 elif self.op == 'move' and self.from_path: 90 ntv = ntv_res[str(self.from_path)] 91 del ntv_res[str(NtvPointer(self.from_path[:-1]))][self.from_path[-1]] 92 ntv.parent = None 93 else: 94 raise NtvOpError('op is not correct') 95 if idx == '-': 96 ntv_res[p_path].append(ntv) 97 else: 98 ntv_res[p_path].insert(idx, ntv) 99 elif self.op == 'test' and self.entity: 100 ntv = Ntv.obj(self.entity) 101 if not (idx == '-' and ntv in ntv_res[p_path]) and not ( 102 isinstance(idx, int) and ntv == ntv_res[path]): 103 raise NtvOpError('test is not correct') 104 elif self.op == 'remove': 105 idx = self.path[-1] 106 idx = len(ntv[p_path]) - 1 if idx == '-' else idx 107 ntv_res[p_path+'/'+str(idx)].remove(index=idx) 108 elif self.op == 'replace' and self.entity: 109 ntv_res[path].replace(Ntv.obj(self.entity)) 110 else: 111 raise NtvOpError('op add no result') 112 return ntv_res
The NtvOp class defines operations to apply to an NTV entity
44 def __init__(self, op, path=None, entity=None, comment=None, from_path=None): 45 op = op.json if isinstance(op, NtvOp) else op 46 dic = isinstance(op, dict) 47 self.op = op.get('op') if dic else op 48 self.entity = op.get('entity') if dic else entity 49 self.comment = op.get('comment') if dic else comment 50 self.from_path = NtvPointer(op.get('from')) if dic else NtvPointer(from_path) 51 self.path = NtvPointer(op.get('path')) if dic else NtvPointer(path) 52 if not self.path or not self.op in OPERATIONS: 53 raise NtvOpError('path or op is not correct')
77 def exe(self, ntv): 78 '''execute the operation with ntv entity and return the resulting entity''' 79 from json_ntv.ntv import Ntv 80 ntv_res = copy(ntv) 81 idx = self.path[-1] 82 p_path = str(NtvPointer(self.path[:-1])) 83 path = str(self.path) 84 if self.op in ['move', 'copy', 'add']: 85 if self.op == 'add' and self.entity: 86 ntv = Ntv.obj(self.entity) 87 elif self.op == 'copy' and self.from_path: 88 ntv = copy(ntv_res[str(self.from_path)]) 89 elif self.op == 'move' and self.from_path: 90 ntv = ntv_res[str(self.from_path)] 91 del ntv_res[str(NtvPointer(self.from_path[:-1]))][self.from_path[-1]] 92 ntv.parent = None 93 else: 94 raise NtvOpError('op is not correct') 95 if idx == '-': 96 ntv_res[p_path].append(ntv) 97 else: 98 ntv_res[p_path].insert(idx, ntv) 99 elif self.op == 'test' and self.entity: 100 ntv = Ntv.obj(self.entity) 101 if not (idx == '-' and ntv in ntv_res[p_path]) and not ( 102 isinstance(idx, int) and ntv == ntv_res[path]): 103 raise NtvOpError('test is not correct') 104 elif self.op == 'remove': 105 idx = self.path[-1] 106 idx = len(ntv[p_path]) - 1 if idx == '-' else idx 107 ntv_res[p_path+'/'+str(idx)].remove(index=idx) 108 elif self.op == 'replace' and self.entity: 109 ntv_res[path].replace(Ntv.obj(self.entity)) 110 else: 111 raise NtvOpError('op add no result') 112 return ntv_res
execute the operation with ntv entity and return the resulting entity
114class NtvPatch: 115 ''' The NtvPatch class defines a sequence of operations to apply to an 116 NTV entity''' 117 118 def __init__(self, list_op=None): 119 list_op = [] if not list_op else list_op 120 self.list_op = [NtvOp(ope) for ope in list_op] 121 122 def __eq__(self, other): 123 ''' equal if list_op are equal''' 124 return self.__class__.__name__ == other.__class__.__name__ and\ 125 self.list_op == other.list_op 126 127 def __copy__(self): 128 ''' Copy all the data ''' 129 cop = self.__class__(self) 130 return cop 131 132 def __setitem__(self, ind, ope): 133 ''' replace op item at the `ind` row with `op`''' 134 if ind < 0 or ind >= len(self): 135 raise NtvOpError("out of bounds") 136 self.list_op[ind] = ope 137 138 def __delitem__(self, ind): 139 '''remove ntv_value item at the `ind` row''' 140 if isinstance(ind, int): 141 self.list_op.pop(ind) 142 else: 143 self.list_op.pop(self.list_op.index(self[ind])) 144 145 def __len__(self): 146 ''' len of list_op''' 147 return len(self.list_op) 148 149 def __str__(self): 150 '''return list of op json format''' 151 return json.dumps([ope.json for ope in self.list_op]) 152 153 def __repr__(self): 154 '''return classname and code''' 155 rep = 'NtvPatch :\n' 156 for ind, op in enumerate(self): 157 rep += ' op' + str(ind).ljust(3, ' ') + ' : ' + repr(op)[5:] + '\n' 158 return rep 159 160 def __contains__(self, item): 161 ''' item of NtvPatch''' 162 return item in self.list_op 163 164 def __iter__(self): 165 ''' iterator for op''' 166 return iter(self.list_op) 167 168 def __getitem__(self, selec): 169 ''' return ntv_value item ''' 170 if selec is None or selec == [] or selec == () or selec == '': 171 return self 172 if isinstance(selec, (list, tuple)) and len(selec) == 1: 173 selec = selec[0] 174 if isinstance(selec, (list, tuple)): 175 return [self[i] for i in selec] 176 return self.list_op[selec] 177 178 def append(self, ope): 179 '''append ope in the NtvPatch''' 180 self.list_op.append(ope) 181 182 def exe(self, ntv): 183 '''execute the included operations with ntv entity and return 184 the resulting entity''' 185 ntv_res = ntv 186 for ope in self: 187 ntv_res = ope.exe(ntv_res) 188 return ntv_res
The NtvPatch class defines a sequence of operations to apply to an NTV entity
182 def exe(self, ntv): 183 '''execute the included operations with ntv entity and return 184 the resulting entity''' 185 ntv_res = ntv 186 for ope in self: 187 ntv_res = ope.exe(ntv_res) 188 return ntv_res
execute the included operations with ntv entity and return the resulting entity
190class NtvPointer(list): 191 192 def __init__(self, pointer): 193 if isinstance(pointer, (list, NtvPointer)): 194 super().__init__(pointer) 195 elif isinstance(pointer, (int, str)): 196 super().__init__(NtvPointer.pointer_list(pointer)) 197 198 def __str__(self): 199 return self.json() 200 201 def json(self, default=''): 202 '''convert a pointer into a json_pointer 203 204 *Parameters* 205 206 - **default**: Str (default '') - default value if pointer is empty 207 ''' 208 return NtvPointer.pointer_json(self) 209 210 def append(self, child): 211 '''append a child pointer into a pointer ''' 212 self += NtvPointer(child) 213 214 @staticmethod 215 def split(path): 216 '''return the last pointer of the path and the path without the last pointer''' 217 pointer = NtvPointer(path) 218 if pointer == []: 219 return (None, None) 220 return (NtvPointer(pointer[-1]), NtvPointer(pointer[:-1])) 221 222 @staticmethod 223 def pointer_json(list_pointer, default=''): 224 '''convert a list of pointer string into a json_pointer 225 226 *Parameters* 227 228 - **default**: Str (default '') - default value if pointer is empty 229 ''' 230 json_p = '' 231 if list_pointer == []: 232 return default 233 for name in list_pointer: 234 json_p += '/' + str(name).replace('~', '~0').replace('/', '~1') 235 return json_p 236 237 @staticmethod 238 def pointer_list(json_pointer): 239 '''convert a json_pointer string into a pointer list''' 240 json_pointer = str(json_pointer) 241 split_pointer = json_pointer.split('/') 242 if len(split_pointer) == 0: 243 return [] 244 if split_pointer[0] != '' and len(split_pointer) > 1: 245 raise NtvOpError("json_pointer is not correct") 246 if split_pointer[0] != '': 247 split_pointer.insert(0, '') 248 return [int(nam) if nam.isdigit() else nam.replace('~1', '/').replace('~0', '/') 249 for nam in split_pointer[1:] ]
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
201 def json(self, default=''): 202 '''convert a pointer into a json_pointer 203 204 *Parameters* 205 206 - **default**: Str (default '') - default value if pointer is empty 207 ''' 208 return NtvPointer.pointer_json(self)
convert a pointer into a json_pointer
Parameters
- default: Str (default '') - default value if pointer is empty
210 def append(self, child): 211 '''append a child pointer into a pointer ''' 212 self += NtvPointer(child)
append a child pointer into a pointer
214 @staticmethod 215 def split(path): 216 '''return the last pointer of the path and the path without the last pointer''' 217 pointer = NtvPointer(path) 218 if pointer == []: 219 return (None, None) 220 return (NtvPointer(pointer[-1]), NtvPointer(pointer[:-1]))
return the last pointer of the path and the path without the last pointer
222 @staticmethod 223 def pointer_json(list_pointer, default=''): 224 '''convert a list of pointer string into a json_pointer 225 226 *Parameters* 227 228 - **default**: Str (default '') - default value if pointer is empty 229 ''' 230 json_p = '' 231 if list_pointer == []: 232 return default 233 for name in list_pointer: 234 json_p += '/' + str(name).replace('~', '~0').replace('/', '~1') 235 return json_p
convert a list of pointer string into a json_pointer
Parameters
- default: Str (default '') - default value if pointer is empty
237 @staticmethod 238 def pointer_list(json_pointer): 239 '''convert a json_pointer string into a pointer list''' 240 json_pointer = str(json_pointer) 241 split_pointer = json_pointer.split('/') 242 if len(split_pointer) == 0: 243 return [] 244 if split_pointer[0] != '' and len(split_pointer) > 1: 245 raise NtvOpError("json_pointer is not correct") 246 if split_pointer[0] != '': 247 split_pointer.insert(0, '') 248 return [int(nam) if nam.isdigit() else nam.replace('~1', '/').replace('~0', '/') 249 for nam in split_pointer[1:] ]
convert a json_pointer string into a pointer list
Inherited Members
- builtins.list
- clear
- copy
- insert
- extend
- pop
- remove
- index
- count
- reverse
- sort
NtvOp Exception
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback