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