NTV.json_ntv.ntv_util
Created on Feb 27 2023
@author: Philippe@loco-labs.io
The ntv_util
module is part of the NTV.json_ntv
package (specification document).
It contains the classes NtvConnector
, NtvTree
, NtvJsonEncoder
and NtvError
for NTV entities.
1# -*- coding: utf-8 -*- 2""" 3Created on Feb 27 2023 4 5@author: Philippe@loco-labs.io 6 7The `ntv_util` module is part of the `NTV.json_ntv` package ([specification document]( 8https://github.com/loco-philippe/NTV/blob/main/documentation/JSON-NTV-standard.pdf)). 9 10It contains the classes `NtvConnector`, `NtvTree`, `NtvJsonEncoder` and `NtvError` 11for NTV entities. 12""" 13from abc import ABC, abstractmethod 14import datetime 15import json 16 17class NtvUtil: 18 ''' The NtvUtil class includes static methods used by several NTV classes ''' 19 20 @staticmethod 21 def from_obj_name(string): 22 '''return a tuple with name, type and separator from string''' 23 if not isinstance(string, str): 24 raise NtvError('a json-name have to be str') 25 if string == '': 26 return (None, None, None) 27 sep = None 28 if '::' in string: 29 sep = '::' 30 elif ':' in string: 31 sep = ':' 32 if sep is None: 33 return (string, None, None) 34 split = string.rsplit(sep, 2) 35 if len(split) == 1: 36 return (string, None, sep) 37 if split[0] == '': 38 return (None, split[1], sep) 39 if split[1] == '': 40 return (split[0], None, sep) 41 return (split[0], split[1], sep) 42 43 44class NtvConnector(ABC): 45 ''' The NtvConnector class is an abstract class used by all NTV connectors 46 for conversion between NTV-JSON data and NTV-OBJ data. 47 48 A NtvConnector child is defined by: 49 - clas_obj: str - define the class name of the object to convert 50 - clas_typ: str - define the NTVtype of the converted object 51 - to_obj_ntv: method - converter from JsonNTV to the object 52 - to_json_ntv: method - converter from the object to JsonNTV 53 54 *class method :* 55 - `connector` 56 - `dic_connec` 57 - `castable` (@property) 58 - `dic_obj` (@property) 59 - `dic_type` (@property) 60 61 *abstract method* 62 - `to_obj_ntv` 63 - `to_json_ntv` 64 65 *static method* 66 - `cast` 67 - `uncast` 68 - `is_json_class` 69 - `is_json` 70 - `init_ntv_keys` 71 ''' 72 73 DIC_NTV_CL = {'NtvSingle': 'ntv', 'NtvList': 'ntv'} 74 DIC_GEO_CL = {'Point': 'point', 'MultiPoint': 'multipoint', 'LineString': 'line', 75 'MultiLineString': 'multiline', 'Polygon': 'polygon', 76 'MultiPolygon': 'multipolygon'} 77 DIC_DAT_CL = {'date': 'date', 'time': 'time', 'datetime': 'datetime'} 78 DIC_FCT = {'date': datetime.date.fromisoformat, 'time': datetime.time.fromisoformat, 79 'datetime': datetime.datetime.fromisoformat} 80 DIC_GEO = {'point': 'point', 'multipoint': 'multipoint', 'line': 'linestring', 81 'multiline': 'multilinestring', 'polygon': 'polygon', 82 'multipolygon': 'multipolygon'} 83 DIC_CBOR = {'point': False, 'multipoint': False, 'line': False, 84 'multiline': False, 'polygon': False, 'multipolygon': False, 85 'date': True, 'time': False, 'datetime': True} 86 DIC_OBJ = {'tab': 'DataFrameConnec', 'field': 'SeriesConnec', 87 'point': 'ShapelyConnec', 'multipoint': 'ShapelyConnec', 88 'line': 'ShapelyConnec', 'multiline': 'ShapelyConnec', 89 'polygon': 'ShapelyConnec', 'multipolygon': 'ShapelyConnec', 90 'other': None} 91 92 @classmethod 93 @property 94 def castable(cls): 95 '''return a list with class_name allowed for json-obj conversion''' 96 return ['str', 'int', 'bool', 'float', 'dict', 'tuple', 'NoneType', 97 'NtvSingle', 'NtvList'] \ 98 + list(NtvConnector.DIC_GEO_CL.keys()) \ 99 + list(NtvConnector.DIC_FCT.keys()) \ 100 + list(NtvConnector.dic_connec().keys()) 101 102 @classmethod 103 @property 104 def dic_obj(cls): 105 '''return a dict with the connectors: { type: class_connec_name }''' 106 return {clas.clas_typ: clas.__name__ for clas in cls.__subclasses__()} |\ 107 NtvConnector.DIC_OBJ 108 109 @classmethod 110 @property 111 def dic_type(cls): 112 '''return a dict with the connectors: { class_obj_name: type }''' 113 return {clas.clas_obj: clas.clas_typ for clas in cls.__subclasses__()} |\ 114 NtvConnector.DIC_GEO_CL | NtvConnector.DIC_DAT_CL | NtvConnector.DIC_NTV_CL 115 116 @classmethod 117 def connector(cls): 118 '''return a dict with the connectors: { class_connec_name: class_connec }''' 119 return {clas.__name__: clas for clas in cls.__subclasses__()} 120 121 @classmethod 122 def dic_connec(cls): 123 '''return a dict with the clas associated to the connector: 124 { class_obj_name: class_connec_name }''' 125 return {clas.clas_obj: clas.__name__ for clas in cls.__subclasses__()} 126 127 @staticmethod 128 @abstractmethod 129 def to_obj_ntv(ntv_value, **kwargs): 130 ''' abstract - convert ntv_value into the return object''' 131 132 @staticmethod 133 @abstractmethod 134 def to_json_ntv(value, name=None, typ=None): 135 ''' abstract - convert NTV object (value, name, type) into the NTV json 136 (json-value, name, type)''' 137 138 @staticmethod 139 def cast(data, name=None, type_str=None): 140 '''return JSON-NTV conversion (json_value, name, type_str) of the NTV entity 141 defined in parameters. 142 143 *Parameters* 144 145 - **data**: NtvSingle entity or NTVvalue of the NTV entity 146 - **name** : String (default None) - name of the NTV entity 147 - **type_str**: String (default None) - type of the NTV entity 148 ''' 149 clas = data.__class__.__name__ 150 if clas == 'NtvSingle': 151 name = data.ntv_name 152 type_str = data.type_str 153 data = data.ntv_value 154 dic_geo_cl = NtvConnector.DIC_GEO_CL 155 dic_connec = NtvConnector.dic_connec() 156 match clas: 157 case 'int' | 'float' | 'bool' | 'str': 158 return (data, name, type_str) 159 case 'dict': 160 return ({key: NtvConnector.cast(val, name, type_str)[0] 161 for key, val in data.items()}, name, type_str) 162 case 'list': 163 return ([NtvConnector.cast(val, name, type_str)[0] for val in data], 164 name, NtvConnector._typ_obj(data) if not type_str else type_str) 165 case 'tuple': 166 return (list(data), name, 'array' if not type_str else type_str) 167 case 'date' | 'time' | 'datetime': 168 return (data.isoformat(), name, clas if not type_str else type_str) 169 case 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | \ 170 'Polygon' | 'MultiPolygon': 171 return (NtvConnector.connector()[dic_connec['geometry']].to_json_ntv(data)[0], 172 name, dic_geo_cl[data.__class__.__name__] if not type_str else type_str) 173 case 'NtvSingle' | 'NtvList': 174 return (data.to_obj(), name, 'ntv' if not type_str else type_str) 175 case _: 176 connec = None 177 if clas in dic_connec and dic_connec[clas] in NtvConnector.connector(): 178 connec = NtvConnector.connector()[dic_connec[clas]] 179 if connec: 180 return connec.to_json_ntv(data, name, type_str) 181 raise NtvError( 182 'connector is not defined for the class : ', clas) 183 return (data, name, type_str) 184 185 @staticmethod 186 def uncast(value, name=None, type_str=None, **kwargs): 187 '''return OBJ-NTV conversion (obj_value, name, type_str) of a NTV entity 188 189 *Parameters* 190 191 - **data**: NtvSingle entity or NTVvalue of the NTV entity 192 - **name** : String (default None) - name of the NTV entity 193 - **type_str**: String (default None) - type of the NTV entity 194 ''' 195 if type_str == 'json': 196 return (value, name, type_str) 197 if value.__class__.__name__ == 'NtvSingle': 198 if not (type_str in set(NtvConnector.dic_type.values()) and 199 NtvConnector.is_json(value) or type_str is None): 200 return (value.ntv_value, value.name, value.type_str) 201 type_str = value.type_str if value.ntv_type else None 202 name = value.ntv_name 203 value = value.ntv_value 204 #dic_obj = NtvConnector.dic_obj 205 option = {'dicobj': {}, 'format': 'json', 'type_obj': False} | kwargs 206 value_obj = NtvConnector._uncast_val(value, type_str, **option) 207 return (value_obj, name, type_str if type_str else NtvConnector._typ_obj(value_obj)) 208 209 @staticmethod 210 def _typ_obj(value): 211 if isinstance(value, dict): 212 return NtvConnector._typ_obj(list(value.values())) 213 if isinstance(value, (tuple, list)): 214 for val in value: 215 typ = NtvConnector._typ_obj(val) 216 if typ: 217 return typ 218 return None 219 return NtvConnector.dic_type.get(value.__class__.__name__) 220 221 @staticmethod 222 def _uncast_val(value, type_n, **option): 223 '''return value from ntv value''' 224 dic_fct = NtvConnector.DIC_FCT 225 dic_geo = NtvConnector.DIC_GEO 226 dic_obj = NtvConnector.dic_obj | option['dicobj'] 227 dic_cbor = NtvConnector.DIC_CBOR 228 if not type_n or type_n == 'json' or (option['format'] == 'cbor' and 229 not dic_cbor.get(type_n, False)): 230 return value 231 if type_n in dic_fct: 232 if isinstance(value, (tuple, list)): 233 return [NtvConnector._uncast_val(val, type_n, **option) for val in value] 234 if isinstance(value, dict): 235 return {key: NtvConnector._uncast_val(val, type_n, **option) 236 for key, val in value.items()} 237 return dic_fct[type_n](value) 238 if type_n == 'array': 239 return tuple(value) 240 if type_n == 'ntv': 241 from json_ntv.ntv import Ntv 242 return Ntv.from_obj(value) 243 if type_n in dic_geo: 244 option['type_geo'] = dic_geo[type_n] 245 connec = None 246 if type_n in dic_obj and \ 247 dic_obj[type_n] in NtvConnector.connector(): 248 connec = NtvConnector.connector()[dic_obj[type_n]] 249 elif dic_obj['other'] in NtvConnector.connector(): 250 connec = NtvConnector.connector()['other'] 251 if connec: 252 return connec.to_obj_ntv(value, **option) 253 #raise NtvError('type of value not allowed for conversion') 254 return value 255 256 @staticmethod 257 def is_json_class(val): 258 ''' return True if val is a json type''' 259 return val is None or isinstance(val, (list, int, str, float, bool, dict)) 260 261 @staticmethod 262 def is_json(obj): 263 ''' check if obj is a json structure and return True if obj is a json-value 264 265 *Parameters* 266 267 - **obj** : object to check 268 - **ntvobj** : boolean (default False) - if True NTV class value are accepted''' 269 if obj is None: 270 return True 271 is_js = NtvConnector.is_json 272 match obj: 273 case str() | int() | float() | bool() as obj: 274 return True 275 case list() | tuple() as obj: 276 if not obj: 277 return True 278 return min(is_js(obj_in) for obj_in in obj) 279 case dict() as obj: 280 if not obj: 281 return True 282 if not min(isinstance(key, str) for key in obj.keys()): 283 raise NtvError('key in dict in not string') 284 return min(is_js(obj_in) for obj_in in obj.values()) 285 case _: 286 if not obj.__class__.__name__ in NtvConnector.castable: 287 raise NtvError(obj.__class__.__name__ + 288 'is not valid for NTV') 289 return False 290 291 @staticmethod 292 def keysfromderkeys(parentkeys, derkeys): 293 '''return keys from parent keys and derkeys 294 295 *Parameters* 296 297 - **parentkeys** : list of keys from parent 298 - **derkeys** : list of derived keys 299 300 *Returns* : list of keys''' 301 return [derkeys[pkey] for pkey in parentkeys] 302 303 @staticmethod 304 def encode_coef(lis): 305 '''Generate a repetition coefficient for periodic list''' 306 if len(lis) < 2: 307 return 0 308 coef = 1 309 while coef != len(lis): 310 if lis[coef-1] != lis[coef]: 311 break 312 coef += 1 313 if (not len(lis) % (coef * (max(lis) + 1)) and 314 lis == NtvConnector.keysfromcoef(coef, max(lis) + 1, len(lis))): 315 return coef 316 return 0 317 318 @staticmethod 319 def keysfromcoef(coef, period, leng=None): 320 ''' return a list of keys with periodic structure''' 321 if not leng: 322 leng = coef * period 323 return None if not (coef and period) else [(ind % (coef * period)) // coef 324 for ind in range(leng)] 325 @staticmethod 326 def init_ntv_keys(ind, lidx, leng): 327 ''' initialization of explicit keys data in lidx object of tabular data''' 328 # name: 0, type: 1, codec: 2, parent: 3, keys: 4, coef: 5, leng: 6 329 name, typ, codec, parent, keys, coef, length = lidx[ind] 330 if (keys, parent, coef) == (None, None, None): # full or unique 331 if len(codec) == 1: # unique 332 lidx[ind][4] = [0] * leng 333 elif len(codec) == leng: # full 334 lidx[ind][4] = list(range(leng)) 335 else: 336 raise NtvError('impossible to generate keys') 337 return 338 if keys and len(keys) > 1 and parent is None: #complete 339 return 340 if coef: #primary 341 lidx[ind][4] = [(ikey % (coef * len(codec))) // coef for ikey in range(leng)] 342 lidx[ind][3] = None 343 return 344 if parent is None: 345 raise NtvError('keys not referenced') 346 if not lidx[parent][4] or len(lidx[parent][4]) != leng: 347 NtvConnector.init_ntv_keys(parent, lidx, leng) 348 if not keys and len(codec) == len(lidx[parent][2]): # implicit 349 lidx[ind][4] = lidx[parent][4] 350 lidx[ind][3] = None 351 return 352 lidx[ind][4] = NtvConnector.keysfromderkeys(lidx[parent][4], keys) # relative 353 #lidx[ind][4] = [keys[pkey] for pkey in lidx[parent][4]] # relative 354 lidx[ind][3] = None 355 return 356 357class NtvTree: 358 ''' The NtvTree class is an iterator class used to traverse a NTV tree structure. 359 Some other methods give tree indicators and data. 360 361 *Attributes :* 362 363 - **ntv** : Ntv entity 364 - **_node**: Ntv entity - node pointer 365 - **_stack**: list - stack used to store context in recursive methods 366 367 *dynamic values (@property)* 368 - `breadth` 369 - `size` 370 - `height` 371 - `adjacency_list` 372 - `nodes` 373 - `leaf_nodes` 374 - `inner_nodes` 375 ''' 376 377 def __init__(self, ntv): 378 ''' the parameter of the constructor is the Ntv entity''' 379 self._ntv = ntv 380 self._node = None 381 self._stack = [] 382 383 def __iter__(self): 384 ''' iterator without initialization''' 385 return self 386 387 def __next__(self): 388 ''' return next node in the tree''' 389 if self._node is None: 390 self._node = self._ntv 391 elif len(self._node) == 0: 392 raise StopIteration 393 elif self._node.__class__.__name__ == 'NtvList': 394 self._next_down() 395 else: 396 self._next_up() 397 return self._node 398 399 def _next_down(self): 400 ''' find the next subchild node''' 401 402 self._node = self._node[0] 403 self._stack.append(0) 404 405 def _next_up(self): 406 ''' find the next sibling or ancestor node''' 407 parent = self._node.parent 408 if not parent or self._node == self._ntv: 409 raise StopIteration 410 ind = self._stack[-1] 411 if ind < len(parent) - 1: # if ind is not the last 412 self._node = parent[ind + 1] 413 self._stack[-1] += 1 414 else: 415 if parent == self._ntv: 416 raise StopIteration 417 self._node = parent 418 self._stack.pop() 419 self._next_up() 420 421 @property 422 def breadth(self): 423 ''' return the number of leaves''' 424 return len(self.leaf_nodes) 425 426 @property 427 def size(self): 428 ''' return the number of nodes''' 429 return len(self.nodes) 430 431 @property 432 def height(self): 433 ''' return the height of the tree''' 434 return max(len(node.pointer()) for node in self.__class__(self._ntv)) 435 436 @property 437 def adjacency_list(self): 438 ''' return a dict with the list of child nodes for each parent node''' 439 return {node: node.val for node in self.inner_nodes} 440 441 @property 442 def nodes(self): 443 ''' return the list of nodes according to the DFS preordering algorithm''' 444 return list(self.__class__(self._ntv)) 445 446 @property 447 def dic_nodes(self): 448 ''' return a dict of nodes according to the DFS preordering algorithm''' 449 return {node.ntv_name: node for node in self.__class__(self._ntv) 450 if node.ntv_name} 451 452 @property 453 def leaf_nodes(self): 454 ''' return the list of leaf nodes according to the DFS preordering algorithm''' 455 #return [node for node in self.__class__(self._ntv) if not isinstance(node, NtvList)] 456 return [node for node in self.__class__(self._ntv) 457 if node.__class__.__name__ == 'NtvSingle'] 458 459 @property 460 def inner_nodes(self): 461 ''' return the list of inner nodes according to the DFS preordering algorithm''' 462 return [node for node in self.__class__(self._ntv) 463 if node.__class__.__name__ == 'NtvList'] 464 465 466 467 468class NtvJsonEncoder(json.JSONEncoder): 469 """json encoder for Ntv data""" 470 471 def default(self, o): 472 try: 473 return NtvConnector.cast(o)[0] 474 except NtvError: 475 return json.JSONEncoder.default(self, o) 476 if isinstance(o, (datetime.datetime, datetime.date, datetime.time)): 477 return o.isoformat() 478 return json.JSONEncoder.default(self, o) 479 480 481class NtvError(Exception): 482 ''' NTV Exception''' 483 # pass
18class NtvUtil: 19 ''' The NtvUtil class includes static methods used by several NTV classes ''' 20 21 @staticmethod 22 def from_obj_name(string): 23 '''return a tuple with name, type and separator from string''' 24 if not isinstance(string, str): 25 raise NtvError('a json-name have to be str') 26 if string == '': 27 return (None, None, None) 28 sep = None 29 if '::' in string: 30 sep = '::' 31 elif ':' in string: 32 sep = ':' 33 if sep is None: 34 return (string, None, None) 35 split = string.rsplit(sep, 2) 36 if len(split) == 1: 37 return (string, None, sep) 38 if split[0] == '': 39 return (None, split[1], sep) 40 if split[1] == '': 41 return (split[0], None, sep) 42 return (split[0], split[1], sep)
The NtvUtil class includes static methods used by several NTV classes
21 @staticmethod 22 def from_obj_name(string): 23 '''return a tuple with name, type and separator from string''' 24 if not isinstance(string, str): 25 raise NtvError('a json-name have to be str') 26 if string == '': 27 return (None, None, None) 28 sep = None 29 if '::' in string: 30 sep = '::' 31 elif ':' in string: 32 sep = ':' 33 if sep is None: 34 return (string, None, None) 35 split = string.rsplit(sep, 2) 36 if len(split) == 1: 37 return (string, None, sep) 38 if split[0] == '': 39 return (None, split[1], sep) 40 if split[1] == '': 41 return (split[0], None, sep) 42 return (split[0], split[1], sep)
return a tuple with name, type and separator from string
45class NtvConnector(ABC): 46 ''' The NtvConnector class is an abstract class used by all NTV connectors 47 for conversion between NTV-JSON data and NTV-OBJ data. 48 49 A NtvConnector child is defined by: 50 - clas_obj: str - define the class name of the object to convert 51 - clas_typ: str - define the NTVtype of the converted object 52 - to_obj_ntv: method - converter from JsonNTV to the object 53 - to_json_ntv: method - converter from the object to JsonNTV 54 55 *class method :* 56 - `connector` 57 - `dic_connec` 58 - `castable` (@property) 59 - `dic_obj` (@property) 60 - `dic_type` (@property) 61 62 *abstract method* 63 - `to_obj_ntv` 64 - `to_json_ntv` 65 66 *static method* 67 - `cast` 68 - `uncast` 69 - `is_json_class` 70 - `is_json` 71 - `init_ntv_keys` 72 ''' 73 74 DIC_NTV_CL = {'NtvSingle': 'ntv', 'NtvList': 'ntv'} 75 DIC_GEO_CL = {'Point': 'point', 'MultiPoint': 'multipoint', 'LineString': 'line', 76 'MultiLineString': 'multiline', 'Polygon': 'polygon', 77 'MultiPolygon': 'multipolygon'} 78 DIC_DAT_CL = {'date': 'date', 'time': 'time', 'datetime': 'datetime'} 79 DIC_FCT = {'date': datetime.date.fromisoformat, 'time': datetime.time.fromisoformat, 80 'datetime': datetime.datetime.fromisoformat} 81 DIC_GEO = {'point': 'point', 'multipoint': 'multipoint', 'line': 'linestring', 82 'multiline': 'multilinestring', 'polygon': 'polygon', 83 'multipolygon': 'multipolygon'} 84 DIC_CBOR = {'point': False, 'multipoint': False, 'line': False, 85 'multiline': False, 'polygon': False, 'multipolygon': False, 86 'date': True, 'time': False, 'datetime': True} 87 DIC_OBJ = {'tab': 'DataFrameConnec', 'field': 'SeriesConnec', 88 'point': 'ShapelyConnec', 'multipoint': 'ShapelyConnec', 89 'line': 'ShapelyConnec', 'multiline': 'ShapelyConnec', 90 'polygon': 'ShapelyConnec', 'multipolygon': 'ShapelyConnec', 91 'other': None} 92 93 @classmethod 94 @property 95 def castable(cls): 96 '''return a list with class_name allowed for json-obj conversion''' 97 return ['str', 'int', 'bool', 'float', 'dict', 'tuple', 'NoneType', 98 'NtvSingle', 'NtvList'] \ 99 + list(NtvConnector.DIC_GEO_CL.keys()) \ 100 + list(NtvConnector.DIC_FCT.keys()) \ 101 + list(NtvConnector.dic_connec().keys()) 102 103 @classmethod 104 @property 105 def dic_obj(cls): 106 '''return a dict with the connectors: { type: class_connec_name }''' 107 return {clas.clas_typ: clas.__name__ for clas in cls.__subclasses__()} |\ 108 NtvConnector.DIC_OBJ 109 110 @classmethod 111 @property 112 def dic_type(cls): 113 '''return a dict with the connectors: { class_obj_name: type }''' 114 return {clas.clas_obj: clas.clas_typ for clas in cls.__subclasses__()} |\ 115 NtvConnector.DIC_GEO_CL | NtvConnector.DIC_DAT_CL | NtvConnector.DIC_NTV_CL 116 117 @classmethod 118 def connector(cls): 119 '''return a dict with the connectors: { class_connec_name: class_connec }''' 120 return {clas.__name__: clas for clas in cls.__subclasses__()} 121 122 @classmethod 123 def dic_connec(cls): 124 '''return a dict with the clas associated to the connector: 125 { class_obj_name: class_connec_name }''' 126 return {clas.clas_obj: clas.__name__ for clas in cls.__subclasses__()} 127 128 @staticmethod 129 @abstractmethod 130 def to_obj_ntv(ntv_value, **kwargs): 131 ''' abstract - convert ntv_value into the return object''' 132 133 @staticmethod 134 @abstractmethod 135 def to_json_ntv(value, name=None, typ=None): 136 ''' abstract - convert NTV object (value, name, type) into the NTV json 137 (json-value, name, type)''' 138 139 @staticmethod 140 def cast(data, name=None, type_str=None): 141 '''return JSON-NTV conversion (json_value, name, type_str) of the NTV entity 142 defined in parameters. 143 144 *Parameters* 145 146 - **data**: NtvSingle entity or NTVvalue of the NTV entity 147 - **name** : String (default None) - name of the NTV entity 148 - **type_str**: String (default None) - type of the NTV entity 149 ''' 150 clas = data.__class__.__name__ 151 if clas == 'NtvSingle': 152 name = data.ntv_name 153 type_str = data.type_str 154 data = data.ntv_value 155 dic_geo_cl = NtvConnector.DIC_GEO_CL 156 dic_connec = NtvConnector.dic_connec() 157 match clas: 158 case 'int' | 'float' | 'bool' | 'str': 159 return (data, name, type_str) 160 case 'dict': 161 return ({key: NtvConnector.cast(val, name, type_str)[0] 162 for key, val in data.items()}, name, type_str) 163 case 'list': 164 return ([NtvConnector.cast(val, name, type_str)[0] for val in data], 165 name, NtvConnector._typ_obj(data) if not type_str else type_str) 166 case 'tuple': 167 return (list(data), name, 'array' if not type_str else type_str) 168 case 'date' | 'time' | 'datetime': 169 return (data.isoformat(), name, clas if not type_str else type_str) 170 case 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | \ 171 'Polygon' | 'MultiPolygon': 172 return (NtvConnector.connector()[dic_connec['geometry']].to_json_ntv(data)[0], 173 name, dic_geo_cl[data.__class__.__name__] if not type_str else type_str) 174 case 'NtvSingle' | 'NtvList': 175 return (data.to_obj(), name, 'ntv' if not type_str else type_str) 176 case _: 177 connec = None 178 if clas in dic_connec and dic_connec[clas] in NtvConnector.connector(): 179 connec = NtvConnector.connector()[dic_connec[clas]] 180 if connec: 181 return connec.to_json_ntv(data, name, type_str) 182 raise NtvError( 183 'connector is not defined for the class : ', clas) 184 return (data, name, type_str) 185 186 @staticmethod 187 def uncast(value, name=None, type_str=None, **kwargs): 188 '''return OBJ-NTV conversion (obj_value, name, type_str) of a NTV entity 189 190 *Parameters* 191 192 - **data**: NtvSingle entity or NTVvalue of the NTV entity 193 - **name** : String (default None) - name of the NTV entity 194 - **type_str**: String (default None) - type of the NTV entity 195 ''' 196 if type_str == 'json': 197 return (value, name, type_str) 198 if value.__class__.__name__ == 'NtvSingle': 199 if not (type_str in set(NtvConnector.dic_type.values()) and 200 NtvConnector.is_json(value) or type_str is None): 201 return (value.ntv_value, value.name, value.type_str) 202 type_str = value.type_str if value.ntv_type else None 203 name = value.ntv_name 204 value = value.ntv_value 205 #dic_obj = NtvConnector.dic_obj 206 option = {'dicobj': {}, 'format': 'json', 'type_obj': False} | kwargs 207 value_obj = NtvConnector._uncast_val(value, type_str, **option) 208 return (value_obj, name, type_str if type_str else NtvConnector._typ_obj(value_obj)) 209 210 @staticmethod 211 def _typ_obj(value): 212 if isinstance(value, dict): 213 return NtvConnector._typ_obj(list(value.values())) 214 if isinstance(value, (tuple, list)): 215 for val in value: 216 typ = NtvConnector._typ_obj(val) 217 if typ: 218 return typ 219 return None 220 return NtvConnector.dic_type.get(value.__class__.__name__) 221 222 @staticmethod 223 def _uncast_val(value, type_n, **option): 224 '''return value from ntv value''' 225 dic_fct = NtvConnector.DIC_FCT 226 dic_geo = NtvConnector.DIC_GEO 227 dic_obj = NtvConnector.dic_obj | option['dicobj'] 228 dic_cbor = NtvConnector.DIC_CBOR 229 if not type_n or type_n == 'json' or (option['format'] == 'cbor' and 230 not dic_cbor.get(type_n, False)): 231 return value 232 if type_n in dic_fct: 233 if isinstance(value, (tuple, list)): 234 return [NtvConnector._uncast_val(val, type_n, **option) for val in value] 235 if isinstance(value, dict): 236 return {key: NtvConnector._uncast_val(val, type_n, **option) 237 for key, val in value.items()} 238 return dic_fct[type_n](value) 239 if type_n == 'array': 240 return tuple(value) 241 if type_n == 'ntv': 242 from json_ntv.ntv import Ntv 243 return Ntv.from_obj(value) 244 if type_n in dic_geo: 245 option['type_geo'] = dic_geo[type_n] 246 connec = None 247 if type_n in dic_obj and \ 248 dic_obj[type_n] in NtvConnector.connector(): 249 connec = NtvConnector.connector()[dic_obj[type_n]] 250 elif dic_obj['other'] in NtvConnector.connector(): 251 connec = NtvConnector.connector()['other'] 252 if connec: 253 return connec.to_obj_ntv(value, **option) 254 #raise NtvError('type of value not allowed for conversion') 255 return value 256 257 @staticmethod 258 def is_json_class(val): 259 ''' return True if val is a json type''' 260 return val is None or isinstance(val, (list, int, str, float, bool, dict)) 261 262 @staticmethod 263 def is_json(obj): 264 ''' check if obj is a json structure and return True if obj is a json-value 265 266 *Parameters* 267 268 - **obj** : object to check 269 - **ntvobj** : boolean (default False) - if True NTV class value are accepted''' 270 if obj is None: 271 return True 272 is_js = NtvConnector.is_json 273 match obj: 274 case str() | int() | float() | bool() as obj: 275 return True 276 case list() | tuple() as obj: 277 if not obj: 278 return True 279 return min(is_js(obj_in) for obj_in in obj) 280 case dict() as obj: 281 if not obj: 282 return True 283 if not min(isinstance(key, str) for key in obj.keys()): 284 raise NtvError('key in dict in not string') 285 return min(is_js(obj_in) for obj_in in obj.values()) 286 case _: 287 if not obj.__class__.__name__ in NtvConnector.castable: 288 raise NtvError(obj.__class__.__name__ + 289 'is not valid for NTV') 290 return False 291 292 @staticmethod 293 def keysfromderkeys(parentkeys, derkeys): 294 '''return keys from parent keys and derkeys 295 296 *Parameters* 297 298 - **parentkeys** : list of keys from parent 299 - **derkeys** : list of derived keys 300 301 *Returns* : list of keys''' 302 return [derkeys[pkey] for pkey in parentkeys] 303 304 @staticmethod 305 def encode_coef(lis): 306 '''Generate a repetition coefficient for periodic list''' 307 if len(lis) < 2: 308 return 0 309 coef = 1 310 while coef != len(lis): 311 if lis[coef-1] != lis[coef]: 312 break 313 coef += 1 314 if (not len(lis) % (coef * (max(lis) + 1)) and 315 lis == NtvConnector.keysfromcoef(coef, max(lis) + 1, len(lis))): 316 return coef 317 return 0 318 319 @staticmethod 320 def keysfromcoef(coef, period, leng=None): 321 ''' return a list of keys with periodic structure''' 322 if not leng: 323 leng = coef * period 324 return None if not (coef and period) else [(ind % (coef * period)) // coef 325 for ind in range(leng)] 326 @staticmethod 327 def init_ntv_keys(ind, lidx, leng): 328 ''' initialization of explicit keys data in lidx object of tabular data''' 329 # name: 0, type: 1, codec: 2, parent: 3, keys: 4, coef: 5, leng: 6 330 name, typ, codec, parent, keys, coef, length = lidx[ind] 331 if (keys, parent, coef) == (None, None, None): # full or unique 332 if len(codec) == 1: # unique 333 lidx[ind][4] = [0] * leng 334 elif len(codec) == leng: # full 335 lidx[ind][4] = list(range(leng)) 336 else: 337 raise NtvError('impossible to generate keys') 338 return 339 if keys and len(keys) > 1 and parent is None: #complete 340 return 341 if coef: #primary 342 lidx[ind][4] = [(ikey % (coef * len(codec))) // coef for ikey in range(leng)] 343 lidx[ind][3] = None 344 return 345 if parent is None: 346 raise NtvError('keys not referenced') 347 if not lidx[parent][4] or len(lidx[parent][4]) != leng: 348 NtvConnector.init_ntv_keys(parent, lidx, leng) 349 if not keys and len(codec) == len(lidx[parent][2]): # implicit 350 lidx[ind][4] = lidx[parent][4] 351 lidx[ind][3] = None 352 return 353 lidx[ind][4] = NtvConnector.keysfromderkeys(lidx[parent][4], keys) # relative 354 #lidx[ind][4] = [keys[pkey] for pkey in lidx[parent][4]] # relative 355 lidx[ind][3] = None 356 return
The NtvConnector class is an abstract class used by all NTV connectors for conversion between NTV-JSON data and NTV-OBJ data.
A NtvConnector child is defined by:
- clas_obj: str - define the class name of the object to convert
- clas_typ: str - define the NTVtype of the converted object
- to_obj_ntv: method - converter from JsonNTV to the object
- to_json_ntv: method - converter from the object to JsonNTV
class method :
connector
dic_connec
castable
(@property)dic_obj
(@property)dic_type
(@property)
abstract method
static method
117 @classmethod 118 def connector(cls): 119 '''return a dict with the connectors: { class_connec_name: class_connec }''' 120 return {clas.__name__: clas for clas in cls.__subclasses__()}
return a dict with the connectors: { class_connec_name: class_connec }
122 @classmethod 123 def dic_connec(cls): 124 '''return a dict with the clas associated to the connector: 125 { class_obj_name: class_connec_name }''' 126 return {clas.clas_obj: clas.__name__ for clas in cls.__subclasses__()}
return a dict with the clas associated to the connector: { class_obj_name: class_connec_name }
128 @staticmethod 129 @abstractmethod 130 def to_obj_ntv(ntv_value, **kwargs): 131 ''' abstract - convert ntv_value into the return object'''
abstract - convert ntv_value into the return object
133 @staticmethod 134 @abstractmethod 135 def to_json_ntv(value, name=None, typ=None): 136 ''' abstract - convert NTV object (value, name, type) into the NTV json 137 (json-value, name, type)'''
abstract - convert NTV object (value, name, type) into the NTV json (json-value, name, type)
139 @staticmethod 140 def cast(data, name=None, type_str=None): 141 '''return JSON-NTV conversion (json_value, name, type_str) of the NTV entity 142 defined in parameters. 143 144 *Parameters* 145 146 - **data**: NtvSingle entity or NTVvalue of the NTV entity 147 - **name** : String (default None) - name of the NTV entity 148 - **type_str**: String (default None) - type of the NTV entity 149 ''' 150 clas = data.__class__.__name__ 151 if clas == 'NtvSingle': 152 name = data.ntv_name 153 type_str = data.type_str 154 data = data.ntv_value 155 dic_geo_cl = NtvConnector.DIC_GEO_CL 156 dic_connec = NtvConnector.dic_connec() 157 match clas: 158 case 'int' | 'float' | 'bool' | 'str': 159 return (data, name, type_str) 160 case 'dict': 161 return ({key: NtvConnector.cast(val, name, type_str)[0] 162 for key, val in data.items()}, name, type_str) 163 case 'list': 164 return ([NtvConnector.cast(val, name, type_str)[0] for val in data], 165 name, NtvConnector._typ_obj(data) if not type_str else type_str) 166 case 'tuple': 167 return (list(data), name, 'array' if not type_str else type_str) 168 case 'date' | 'time' | 'datetime': 169 return (data.isoformat(), name, clas if not type_str else type_str) 170 case 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | \ 171 'Polygon' | 'MultiPolygon': 172 return (NtvConnector.connector()[dic_connec['geometry']].to_json_ntv(data)[0], 173 name, dic_geo_cl[data.__class__.__name__] if not type_str else type_str) 174 case 'NtvSingle' | 'NtvList': 175 return (data.to_obj(), name, 'ntv' if not type_str else type_str) 176 case _: 177 connec = None 178 if clas in dic_connec and dic_connec[clas] in NtvConnector.connector(): 179 connec = NtvConnector.connector()[dic_connec[clas]] 180 if connec: 181 return connec.to_json_ntv(data, name, type_str) 182 raise NtvError( 183 'connector is not defined for the class : ', clas) 184 return (data, name, type_str)
return JSON-NTV conversion (json_value, name, type_str) of the NTV entity defined in parameters.
Parameters
- data: NtvSingle entity or NTVvalue of the NTV entity
- name : String (default None) - name of the NTV entity
- type_str: String (default None) - type of the NTV entity
186 @staticmethod 187 def uncast(value, name=None, type_str=None, **kwargs): 188 '''return OBJ-NTV conversion (obj_value, name, type_str) of a NTV entity 189 190 *Parameters* 191 192 - **data**: NtvSingle entity or NTVvalue of the NTV entity 193 - **name** : String (default None) - name of the NTV entity 194 - **type_str**: String (default None) - type of the NTV entity 195 ''' 196 if type_str == 'json': 197 return (value, name, type_str) 198 if value.__class__.__name__ == 'NtvSingle': 199 if not (type_str in set(NtvConnector.dic_type.values()) and 200 NtvConnector.is_json(value) or type_str is None): 201 return (value.ntv_value, value.name, value.type_str) 202 type_str = value.type_str if value.ntv_type else None 203 name = value.ntv_name 204 value = value.ntv_value 205 #dic_obj = NtvConnector.dic_obj 206 option = {'dicobj': {}, 'format': 'json', 'type_obj': False} | kwargs 207 value_obj = NtvConnector._uncast_val(value, type_str, **option) 208 return (value_obj, name, type_str if type_str else NtvConnector._typ_obj(value_obj))
return OBJ-NTV conversion (obj_value, name, type_str) of a NTV entity
Parameters
- data: NtvSingle entity or NTVvalue of the NTV entity
- name : String (default None) - name of the NTV entity
- type_str: String (default None) - type of the NTV entity
257 @staticmethod 258 def is_json_class(val): 259 ''' return True if val is a json type''' 260 return val is None or isinstance(val, (list, int, str, float, bool, dict))
return True if val is a json type
262 @staticmethod 263 def is_json(obj): 264 ''' check if obj is a json structure and return True if obj is a json-value 265 266 *Parameters* 267 268 - **obj** : object to check 269 - **ntvobj** : boolean (default False) - if True NTV class value are accepted''' 270 if obj is None: 271 return True 272 is_js = NtvConnector.is_json 273 match obj: 274 case str() | int() | float() | bool() as obj: 275 return True 276 case list() | tuple() as obj: 277 if not obj: 278 return True 279 return min(is_js(obj_in) for obj_in in obj) 280 case dict() as obj: 281 if not obj: 282 return True 283 if not min(isinstance(key, str) for key in obj.keys()): 284 raise NtvError('key in dict in not string') 285 return min(is_js(obj_in) for obj_in in obj.values()) 286 case _: 287 if not obj.__class__.__name__ in NtvConnector.castable: 288 raise NtvError(obj.__class__.__name__ + 289 'is not valid for NTV') 290 return False
check if obj is a json structure and return True if obj is a json-value
Parameters
- obj : object to check
- ntvobj : boolean (default False) - if True NTV class value are accepted
292 @staticmethod 293 def keysfromderkeys(parentkeys, derkeys): 294 '''return keys from parent keys and derkeys 295 296 *Parameters* 297 298 - **parentkeys** : list of keys from parent 299 - **derkeys** : list of derived keys 300 301 *Returns* : list of keys''' 302 return [derkeys[pkey] for pkey in parentkeys]
return keys from parent keys and derkeys
Parameters
- parentkeys : list of keys from parent
- derkeys : list of derived keys
Returns : list of keys
304 @staticmethod 305 def encode_coef(lis): 306 '''Generate a repetition coefficient for periodic list''' 307 if len(lis) < 2: 308 return 0 309 coef = 1 310 while coef != len(lis): 311 if lis[coef-1] != lis[coef]: 312 break 313 coef += 1 314 if (not len(lis) % (coef * (max(lis) + 1)) and 315 lis == NtvConnector.keysfromcoef(coef, max(lis) + 1, len(lis))): 316 return coef 317 return 0
Generate a repetition coefficient for periodic list
319 @staticmethod 320 def keysfromcoef(coef, period, leng=None): 321 ''' return a list of keys with periodic structure''' 322 if not leng: 323 leng = coef * period 324 return None if not (coef and period) else [(ind % (coef * period)) // coef 325 for ind in range(leng)]
return a list of keys with periodic structure
326 @staticmethod 327 def init_ntv_keys(ind, lidx, leng): 328 ''' initialization of explicit keys data in lidx object of tabular data''' 329 # name: 0, type: 1, codec: 2, parent: 3, keys: 4, coef: 5, leng: 6 330 name, typ, codec, parent, keys, coef, length = lidx[ind] 331 if (keys, parent, coef) == (None, None, None): # full or unique 332 if len(codec) == 1: # unique 333 lidx[ind][4] = [0] * leng 334 elif len(codec) == leng: # full 335 lidx[ind][4] = list(range(leng)) 336 else: 337 raise NtvError('impossible to generate keys') 338 return 339 if keys and len(keys) > 1 and parent is None: #complete 340 return 341 if coef: #primary 342 lidx[ind][4] = [(ikey % (coef * len(codec))) // coef for ikey in range(leng)] 343 lidx[ind][3] = None 344 return 345 if parent is None: 346 raise NtvError('keys not referenced') 347 if not lidx[parent][4] or len(lidx[parent][4]) != leng: 348 NtvConnector.init_ntv_keys(parent, lidx, leng) 349 if not keys and len(codec) == len(lidx[parent][2]): # implicit 350 lidx[ind][4] = lidx[parent][4] 351 lidx[ind][3] = None 352 return 353 lidx[ind][4] = NtvConnector.keysfromderkeys(lidx[parent][4], keys) # relative 354 #lidx[ind][4] = [keys[pkey] for pkey in lidx[parent][4]] # relative 355 lidx[ind][3] = None 356 return
initialization of explicit keys data in lidx object of tabular data
358class NtvTree: 359 ''' The NtvTree class is an iterator class used to traverse a NTV tree structure. 360 Some other methods give tree indicators and data. 361 362 *Attributes :* 363 364 - **ntv** : Ntv entity 365 - **_node**: Ntv entity - node pointer 366 - **_stack**: list - stack used to store context in recursive methods 367 368 *dynamic values (@property)* 369 - `breadth` 370 - `size` 371 - `height` 372 - `adjacency_list` 373 - `nodes` 374 - `leaf_nodes` 375 - `inner_nodes` 376 ''' 377 378 def __init__(self, ntv): 379 ''' the parameter of the constructor is the Ntv entity''' 380 self._ntv = ntv 381 self._node = None 382 self._stack = [] 383 384 def __iter__(self): 385 ''' iterator without initialization''' 386 return self 387 388 def __next__(self): 389 ''' return next node in the tree''' 390 if self._node is None: 391 self._node = self._ntv 392 elif len(self._node) == 0: 393 raise StopIteration 394 elif self._node.__class__.__name__ == 'NtvList': 395 self._next_down() 396 else: 397 self._next_up() 398 return self._node 399 400 def _next_down(self): 401 ''' find the next subchild node''' 402 403 self._node = self._node[0] 404 self._stack.append(0) 405 406 def _next_up(self): 407 ''' find the next sibling or ancestor node''' 408 parent = self._node.parent 409 if not parent or self._node == self._ntv: 410 raise StopIteration 411 ind = self._stack[-1] 412 if ind < len(parent) - 1: # if ind is not the last 413 self._node = parent[ind + 1] 414 self._stack[-1] += 1 415 else: 416 if parent == self._ntv: 417 raise StopIteration 418 self._node = parent 419 self._stack.pop() 420 self._next_up() 421 422 @property 423 def breadth(self): 424 ''' return the number of leaves''' 425 return len(self.leaf_nodes) 426 427 @property 428 def size(self): 429 ''' return the number of nodes''' 430 return len(self.nodes) 431 432 @property 433 def height(self): 434 ''' return the height of the tree''' 435 return max(len(node.pointer()) for node in self.__class__(self._ntv)) 436 437 @property 438 def adjacency_list(self): 439 ''' return a dict with the list of child nodes for each parent node''' 440 return {node: node.val for node in self.inner_nodes} 441 442 @property 443 def nodes(self): 444 ''' return the list of nodes according to the DFS preordering algorithm''' 445 return list(self.__class__(self._ntv)) 446 447 @property 448 def dic_nodes(self): 449 ''' return a dict of nodes according to the DFS preordering algorithm''' 450 return {node.ntv_name: node for node in self.__class__(self._ntv) 451 if node.ntv_name} 452 453 @property 454 def leaf_nodes(self): 455 ''' return the list of leaf nodes according to the DFS preordering algorithm''' 456 #return [node for node in self.__class__(self._ntv) if not isinstance(node, NtvList)] 457 return [node for node in self.__class__(self._ntv) 458 if node.__class__.__name__ == 'NtvSingle'] 459 460 @property 461 def inner_nodes(self): 462 ''' return the list of inner nodes according to the DFS preordering algorithm''' 463 return [node for node in self.__class__(self._ntv) 464 if node.__class__.__name__ == 'NtvList']
The NtvTree class is an iterator class used to traverse a NTV tree structure. Some other methods give tree indicators and data.
Attributes :
- ntv : Ntv entity
- _node: Ntv entity - node pointer
- _stack: list - stack used to store context in recursive methods
dynamic values (@property)
469class NtvJsonEncoder(json.JSONEncoder): 470 """json encoder for Ntv data""" 471 472 def default(self, o): 473 try: 474 return NtvConnector.cast(o)[0] 475 except NtvError: 476 return json.JSONEncoder.default(self, o) 477 if isinstance(o, (datetime.datetime, datetime.date, datetime.time)): 478 return o.isoformat() 479 return json.JSONEncoder.default(self, o)
json encoder for Ntv data
472 def default(self, o): 473 try: 474 return NtvConnector.cast(o)[0] 475 except NtvError: 476 return json.JSONEncoder.default(self, o) 477 if isinstance(o, (datetime.datetime, datetime.date, datetime.time)): 478 return o.isoformat() 479 return json.JSONEncoder.default(self, o)
Implement this method in a subclass such that it returns
a serializable object for o
, or calls the base implementation
(to raise a TypeError
).
For example, to support arbitrary iterators, you could implement default like this::
def default(self, o):
try:
iterable = iter(o)
except TypeError:
pass
else:
return list(iterable)
# Let the base class default method raise the TypeError
return JSONEncoder.default(self, o)
Inherited Members
- json.encoder.JSONEncoder
- JSONEncoder
- encode
- iterencode
NTV Exception
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback