ntv-numpy.ntv_numpy.ndarray
@author: Philippe@loco-labs.io
The ndarray
module is part of the ntv-numpy.ntv_numpy
package
(specification document).
It contains the classes Ndarray
, Nutil
, NdarrayError
for the JSON interface
of numpy.ndarrays.
For more information, see the user guide or the github repository.
1# -*- coding: utf-8 -*- 2""" 3@author: Philippe@loco-labs.io 4 5The `ndarray` module is part of the `ntv-numpy.ntv_numpy` package 6([specification document]( 7https://loco-philippe.github.io/ES/JSON%20semantic%20format%20(JSON-NTV).htm)). 8 9It contains the classes `Ndarray`, `Nutil`, `NdarrayError` for the JSON interface 10of numpy.ndarrays. 11 12For more information, see the 13[user guide](https://loco-philippe.github.io/ntv-numpy/docs/user_guide.html) 14 or the [github repository](https://github.com/loco-philippe/ntv-numpy). 15 16""" 17 18import datetime 19import json 20 21from decimal import Decimal 22import numpy as np 23from json_ntv import Ntv, ShapelyConnec, NtvConnector # , Datatype 24from ntv_numpy.data_array import Dfull, Dcomplete, Darray, Dutil 25from ntv_numpy.ndtype import Ndtype, NP_NTYPE 26 27 28class Ndarray: 29 ''' The Ndarray class is the JSON interface of numpy.ndarrays. 30 31 *static methods* 32 - `read_json` 33 - `to_json` 34 - `set_shape` 35 ''' 36 37 def __init__(self, dar, ntv_type=None, shape=None, str_uri=True): 38 '''Ndarray constructor. 39 40 *Parameters* 41 42 - **dar**: Darray or np.ndarray - data to represent 43 - **shape** : list of integer (default None) - length of dimensions 44 - **ntv_type**: string (default None) - NTVtype to apply 45 - **str_uri**: boolean(default True) - if True and dar is a string, 46 dar is an uri else a np.array 47 ''' 48 dar = [None] if isinstance(dar, list) and len(dar) == 0 else dar 49 if isinstance(dar, Ndarray): 50 self.uri = dar.uri 51 self.is_json = dar.is_json 52 self.ntvtype = dar.ntvtype 53 self.shape = dar.shape 54 self.darray = dar.darray 55 return 56 if isinstance(dar, str) and str_uri: 57 self.uri = dar 58 self.is_json = True 59 self.ntvtype = Ndtype(ntv_type) if ntv_type else None 60 self.shape = shape 61 self.darray = None 62 return 63 if shape: 64 dar = Dfull(dar, dtype=Nutil.dtype(ntv_type), unidim=True).data 65 else: 66 dar = np.array(dar, dtype=Nutil.dtype(ntv_type)) 67 shape = list(dar.shape) 68 dar = np.array(dar).reshape(-1) 69 ntv_type = Nutil.nda_ntv_type(dar, ntv_type) 70 self.uri = None 71 self.is_json = Nutil.is_json(dar[0]) 72 self.ntvtype = Ndtype(ntv_type) 73 self.shape = shape 74 self.darray = dar.astype(Nutil.dtype(str(self.ntvtype))) 75 76 def __repr__(self): 77 '''return classname, the shape and the ntv_type''' 78 uri = self.uri if self.uri else '' 79 typ = self.ntv_type if self.ntv_type else '' 80 sha = str(self.shape) if self.shape else '' 81 u_t = ', ' if uri and typ + sha else '' 82 t_s = ', ' if typ and sha else '' 83 return self.__class__.__name__ + '(' + uri + u_t + typ + t_s + sha + ')' 84 85 def __str__(self): 86 '''return json string format''' 87 return json.dumps(self.to_json()) 88 89 def __eq__(self, other): 90 ''' equal if attributes are equal''' 91 if self.ntv_type != other.ntv_type: 92 return False 93 if self.uri != other.uri: 94 return False 95 if self.shape != other.shape: 96 return False 97 if self.darray is None and other.darray is None: 98 return True 99 if self.darray is None or other.darray is None: 100 return False 101 return Dutil.equals(self.darray, other.darray) 102 103 def __len__(self): 104 ''' len of ndarray''' 105 return len(self.darray) if self.darray is not None else 0 106 107 def __contains__(self, item): 108 ''' item of darray values''' 109 return item in self.darray if self.darray is not None else None 110 111 def __getitem__(self, ind): 112 ''' return darray value item''' 113 if self.darray is None: 114 return None 115 if isinstance(ind, tuple): 116 return [self.darray[i] for i in ind] 117 return self.darray[ind] 118 119 def __copy__(self): 120 ''' Copy all the data ''' 121 return self.__class__(self) 122 123 def __array__(self): 124 '''numpy array interface''' 125 return self.ndarray 126 127 @property 128 def ntv_type(self): 129 ''' string representation of ntvtype''' 130 return str(self.ntvtype) if self.ntvtype else None 131 132 @property 133 def ndarray(self): 134 '''representation with a np.ndarray not flattened''' 135 return self.darray.reshape(self.shape) if self.darray is not None else None 136 137 def set_shape(self, shape): 138 '''update the shape''' 139 if Ndarray.len_shape(shape) != len(self.darray): 140 raise NdarrayError( 141 "shape is not consistent with the ndarray length") 142 self.shape = list(shape) 143 144 def update(self, nda, nda_uri=True): 145 '''update uri and darray and return the result (True, False) 146 147 *Parameters* 148 149 - **nda** : string, list, np.ndarray, Ndarray - data to include 150 - **nda_uri** : boolean (default True) - if True, existing shape and 151 ntv_type are not updated (but are created if not existing)''' 152 if not nda_uri and not (self.shape is None or nda.shape is None 153 ) and self.shape != nda.shape: 154 return False 155 if not nda_uri and not (self.ntv_type is None or nda.ntv_type is None 156 ) and self.ntv_type != nda.ntv_type: 157 return False 158 if nda_uri: 159 len_s = self.len_shape(self.shape) 160 if len_s and len(nda) and len_s != len(nda): 161 return False 162 self.ntvtype = nda.ntvtype if self.ntv_type is None else self.ntvtype 163 self.shape = nda.shape if self.shape is None else self.shape 164 else: 165 self.ntvtype = nda.ntvtype if nda.ntv_type is not None else self.ntvtype 166 self.shape = nda.shape if nda.shape is not None else self.shape 167 self.uri, self.darray = ( 168 nda.uri, None) if nda.uri else (None, nda.darray) 169 return True 170 171 def set_array(self, darray): 172 '''set a new darray and remove uri, return the result (True, False) 173 174 *Parameters* 175 176 - **darray** : list, np.ndarray, Ndarray - data to include''' 177 ndarray = Ndarray(darray) 178 darray = ndarray.darray 179 ntv_type = ndarray.ntv_type 180 shape = ndarray.shape 181 new_shape = shape if self.shape is None else self.shape 182 new_ntv_type = ntv_type if self.ntv_type is None else self.ntv_type 183 if (len(darray) != Ndarray.len_shape(new_shape) or 184 new_ntv_type != ntv_type or new_shape != shape): 185 return False 186 self.uri = None 187 self.darray = darray 188 self.ntvtype = Ndtype(new_ntv_type) 189 self.shape = new_shape 190 return True 191 192 def set_uri(self, uri, no_ntv_type=False, no_shape=False): 193 '''set a new uri and remove ndarray and optionaly ntv_type and shape. 194 Return the result (True, False) 195 196 *Parameters* 197 198 - **uri** : string - URI of the Ndarray 199 - **no_ntv_type** : boolean (default False) - If True, ntv_type is None 200 - **no_shape** : boolean (default False) - If True, shape is None 201 ''' 202 if not isinstance(uri, str) or not uri: 203 return False 204 self.uri = uri 205 self.darray = None 206 self.ntvtype = None if no_ntv_type else self.ntvtype 207 self.shape = None if no_shape else self.shape 208 return True 209 210 def to_ndarray(self): 211 '''representation with a np.ndarray not flattened''' 212 return self.ndarray 213 214 @property 215 def mode(self): 216 '''representation mode of the darray/uri data (relative, absolute, 217 undefined, inconsistent)''' 218 match [self.darray, self.uri]: 219 case [None, str()]: 220 return 'relative' 221 case [None, None]: 222 return 'undefined' 223 case [_, None]: 224 return 'absolute' 225 case _: 226 return 'inconsistent' 227 228 @staticmethod 229 def read_json(jsn, **kwargs): 230 ''' convert json ntv_value into a ndarray. 231 232 *Parameters* 233 234 - **convert** : boolean (default True) - If True, convert json data with 235 non Numpy ntv_type into data with python type 236 ''' 237 option = {'convert': True} | kwargs 238 jso = json.loads(jsn) if isinstance(jsn, str) else jsn 239 ntv_value, = Ntv.decode_json(jso)[:1] 240 241 ntv_type = None 242 shape = None 243 match ntv_value[:-1]: 244 case []: ... 245 case [ntv_type, shape]: ... 246 case [str(ntv_type)]: ... 247 case [list(shape)]: ... 248 unidim = shape is not None 249 if isinstance(ntv_value[-1], str): 250 return Ndarray(ntv_value[-1], shape=shape, ntv_type=ntv_type) 251 darray = Darray.read_json(ntv_value[-1], dtype=Nutil.dtype(ntv_type), 252 unidim=unidim) 253 darray.data = Nutil.convert(ntv_type, darray.data, tojson=False, 254 convert=option['convert']) 255 return Ndarray(darray.values, shape=shape, ntv_type=ntv_type) 256 257 def to_json(self, **kwargs): 258 ''' convert a Ndarray into json-value 259 260 *Parameters* 261 262 - **noshape** : Boolean (default True) - if True, without shape if dim < 1 263 - **notype** : Boolean (default False) - including data type if False 264 - **novalue** : Boolean (default False) - including value if False 265 - **format** : string (default 'full') - representation format of the ndarray, 266 - **encoded** : Boolean (default False) - json-value if False else json-text 267 - **header** : Boolean (default True) - including ndarray type 268 ''' 269 option = {'format': 'full', 'header': True, 'encoded': False, 270 'notype': False, 'noshape': True, 'novalue': False} | kwargs 271 if self.mode in ['undefined', 'inconsistent']: 272 return None 273 if self.mode == 'absolute' and len(self.darray) == 0: 274 return [[]] 275 276 shape = None if not self.shape or (len(self.shape) < 2 and 277 option['noshape']) else self.shape 278 279 if self.mode == 'relative': 280 js_val = self.uri 281 else: 282 js_val = Nutil.ntv_val(self.ntv_type, self.darray, option['format'], 283 self.is_json) if not option['novalue'] else ['-'] 284 285 lis = [self.ntv_type if not option['notype'] else None, shape, js_val] 286 return Nutil.json_ntv(None, 'ndarray', 287 [val for val in lis if val is not None], 288 header=option['header'], encoded=option['encoded']) 289 290 @property 291 def info(self): 292 ''' infos of the Ndarray''' 293 inf = {'shape': self.shape} 294 inf['length'] = len(self) 295 inf['ntvtype'] = self.ntv_type 296 inf['shape'] = self.shape 297 inf['uri'] = self.uri 298 return {key: val for key, val in inf.items() if val} 299 300 @staticmethod 301 def len_shape(shape): 302 '''return a length from a shape (product of dimensions)''' 303 if not shape: 304 return 0 305 prod = 1 306 for dim in shape: 307 prod *= dim 308 return prod 309 310 311class Nutil: 312 '''ntv-ndarray utilities. 313 314 *static methods* 315 - `convert` 316 - `is_json` 317 - `ntv_val` 318 - `add_ext` 319 - `split_type` 320 - `ntv_type` 321 - `nda_ntv_type` 322 - `dtype` 323 - `json_ntv` 324 - `split_name` 325 - `split_json_name` 326 327 ''' 328 CONNECTOR_DT = {'field': 'Series', 'tab': 'DataFrame'} 329 PYTHON_DT = {'array': 'list', 'time': 'datetime.time', 330 'object': 'dict', 'null': 'NoneType', 'decimal64': 'Decimal', 331 'ndarray': 'ndarray', 'narray': 'narray'} 332 LOCATION_DT = {'point': 'Point', 333 'line': 'LineString', 'polygon': 'Polygon'} 334 DT_CONNECTOR = {val: key for key, val in CONNECTOR_DT.items()} 335 DT_PYTHON = {val: key for key, val in PYTHON_DT.items()} 336 DT_LOCATION = {val: key for key, val in LOCATION_DT.items()} 337 DT_NTVTYPE = DT_LOCATION | DT_CONNECTOR | DT_PYTHON 338 339 FORMAT_CLS = {'full': Dfull, 'complete': Dcomplete} 340 STRUCT_DT = {'Ntv': 'object', 'NtvSingle': 'object', 'NtvList': 'object'} 341 CONVERT_DT = {'object': 'object', 'array': 'object', 'json': 'object', 342 'number': 'float', 'boolean': 'bool', 'null': 'object', 343 'string': 'str', 'integer': 'int'} 344 345 @staticmethod 346 def is_json(obj): 347 ''' check if obj is a json structure and return True if obj is a json-value 348 349 *Parameters* 350 351 - **obj** : object to check''' 352 if obj is None: 353 return True 354 is_js = NtvConnector.is_json 355 match obj: 356 case str() | int() | float() | bool(): 357 return True 358 case list() | tuple() as obj: 359 if not obj: 360 return True 361 return min(is_js(obj_in) for obj_in in obj) 362 case dict() as obj: 363 if not obj: 364 return True 365 if not min(isinstance(key, str) for key in obj.keys()): 366 return False 367 return min(is_js(obj_in) for obj_in in obj.values()) 368 case _: 369 return False 370 371 @staticmethod 372 def extend_array(arr, til, shap, order): 373 '''return a flattened np.ndarray extended in additional dimensions 374 375 parameters: 376 377 - arr: np.array to extend 378 - til: integer - parameter to apply to np.tile function 379 - shap: list of integer - shape of the array 380 - order: list of integer - order of dimensions to apply 381 ''' 382 old_order = list(range(len(order))) 383 arr_tab = np.tile(arr, til).reshape(shap) 384 return np.moveaxis(arr_tab, old_order, order).flatten() 385 386 @staticmethod 387 def convert(ntv_type, nda, tojson=True, convert=True): 388 ''' convert np.ndarray with external NTVtype. 389 390 *Parameters* 391 392 - **ntv_type** : string - NTVtype deduced from the np.ndarray name_type and dtype, 393 - **nda** : np.ndarray to be converted. 394 - **tojson** : boolean (default True) - apply to json function 395 - **convert** : boolean (default True) - If True, convert json data with 396 non Numpy ntv_type into data with python type 397 ''' 398 399 dtype = Nutil.dtype(ntv_type) 400 jtype = Nutil.dtype(ntv_type, convert=False) 401 if tojson: 402 match ntv_type: 403 case dat if Ndtype(dat).category == 'datation': 404 return nda.astype(dtype).astype(jtype) 405 case 'base16': 406 return nda.astype(dtype) 407 case 'time' | 'decimal64': 408 return nda.astype(jtype) 409 case 'geojson': 410 return np.frompyfunc(ShapelyConnec.to_geojson, 1, 1)(nda) 411 case _: 412 return nda 413 else: 414 match [ntv_type, convert]: 415 case [None, _]: 416 return nda 417 case [_, False]: 418 return nda.astype(jtype) 419 case ['time', _]: 420 return np.frompyfunc(datetime.time.fromisoformat, 1, 1)(nda) 421 case ['decimal64', _]: 422 return np.frompyfunc(Decimal, 1, 1)(nda) 423 case ['narray', _]: 424 nar = np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 425 return np.frompyfunc(Ndarray.to_ndarray, 1, 1)(nar) 426 case ['ndarray', _]: 427 return np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 428 case [('point' | 'line' | 'polygon' | 'geometry'), _]: 429 return np.frompyfunc(ShapelyConnec.to_geometry, 1, 1)(nda) 430 case [connec, _] if connec in Nutil.CONNECTOR_DT: 431 return np.fromiter([NtvConnector.uncast(nd, None, connec)[0] 432 for nd in nda], dtype='object') 433 case _: 434 return nda.astype(dtype) 435 436 # float.fromhex(x.hex()) == x, bytes(bytearray.fromhex(x.hex())) == x 437 @staticmethod 438 def ntv_val(ntv_type, nda, form, is_json=False): 439 ''' convert a np.ndarray into NTV json-value. 440 441 *Parameters* 442 443 - **ntv_type** : string - NTVtype deduced from the ndarray, name_type and dtype, 444 - **nda** : ndarray to be converted. 445 - **form** : format of data ('full', 'complete', 'sparse', 'primary'). 446 - **is_json** : boolean (defaut False) - True if nda data is Json data 447 ''' 448 if form == 'complete' and len(nda) < 2: 449 raise NdarrayError( 450 "complete format is not available with ndarray length < 2") 451 Format = Nutil.FORMAT_CLS[form] 452 darray = Format(nda) 453 ref = darray.ref 454 coding = darray.coding 455 if is_json: 456 return Format(darray.data, ref=ref, coding=coding).to_json() 457 match ntv_type: 458 case 'narray': 459 data = [Ndarray(nd).to_json(header=False) 460 for nd in darray.data] 461 case 'ndarray': 462 data = [Ndarray(nd).to_json(header=False) 463 for nd in darray.data] 464 case connec if connec in Nutil.CONNECTOR_DT: 465 data = [NtvConnector.cast(nd, None, connec)[0] 466 for nd in darray.data] 467 case 'point' | 'line' | 'polygon' | 'geometry': 468 data = np.frompyfunc(ShapelyConnec.to_coord, 1, 1)(darray.data) 469 case None: 470 data = nda 471 case _: 472 data = Nutil.convert(ntv_type, darray.data) 473 return Format(data, ref=ref, coding=coding).to_json() 474 475 @staticmethod 476 def add_ext(typ, ext): 477 '''return extended type string: "typ[ext]"''' 478 ext = '[' + ext + ']' if ext else '' 479 return '' if not typ else typ + ext 480 481 @staticmethod 482 def split_type(typ): 483 '''return a tuple with typ and extension''' 484 if not isinstance(typ, str): 485 return (None, None) 486 spl = typ.split('[', maxsplit=1) 487 return (spl[0], None) if len(spl) == 1 else (spl[0], spl[1][:-1]) 488 489 @staticmethod 490 def split_json_name(string, notnone=False): 491 '''return a tuple with name, ntv_type from string''' 492 null = '' if notnone else None 493 if not string or string == ':': 494 return (null, null) 495 spl = string.rsplit(':', maxsplit=1) 496 if len(spl) == 1: 497 return (string, null) 498 if spl[0] == '': 499 return (null, spl[1]) 500 sp0 = spl[0][:-1] if spl[0][-1] == ':' else spl[0] 501 return (null if sp0 == '' else sp0, null if spl[1] == '' else spl[1]) 502 503 @staticmethod 504 def split_name(string): 505 '''return a list with name, add_name from string''' 506 if not string or string == '.': 507 return ['', ''] 508 spl = string.split('.', maxsplit=1) 509 spl = [spl[0], ''] if len(spl) < 2 else spl 510 return spl 511 512 @staticmethod 513 def ntv_type(dtype, ntv_type=None, ext=None): 514 ''' return ntv_type string from dtype, additional type and extension. 515 516 *Parameters* 517 518 - **dtype** : string - dtype of the ndarray 519 - **ntv_type** : string - additional type 520 - **ext** : string - type extension 521 ''' 522 np_ntype = NP_NTYPE | Nutil.DT_NTVTYPE | { 523 'int': 'int', 'object': 'object'} 524 if ntv_type: 525 return Nutil.add_ext(ntv_type, ext) 526 match dtype: 527 case string if string[:3] == 'str': 528 return Nutil.add_ext('string', ext) 529 case bytesxx if bytesxx[:5] == 'bytes': 530 return Nutil.add_ext('base16', ext) 531 case dtyp if dtyp in np_ntype: 532 return Nutil.add_ext(np_ntype[dtyp], ext) 533 case date if date[:10] == 'datetime64': 534 return 'datetime' + date[10:] 535 case delta if delta[:11] == 'timedelta64': 536 return 'timedelta' + delta[11:] 537 case _: 538 return Nutil.add_ext(dtype, ext) 539 540 @staticmethod 541 def nda_ntv_type(nda, ntv_type=None, ext=None): 542 '''return ntv_type string from an ndarray, additional type and extension. 543 544 *Parameters* 545 546 - **nda** : ndarray - data used to calculate the ntv_type 547 - **ntv_type** : string - additional type 548 - **ext** : string - type extension 549 ''' 550 if ntv_type or nda is None: 551 return ntv_type 552 dtype = nda.dtype.name 553 pytype = nda.flat[0].__class__.__name__ 554 dtype = pytype if dtype == 'object' and pytype not in Nutil.STRUCT_DT else dtype 555 return Nutil.ntv_type(dtype, ntv_type, ext) 556 557 @staticmethod 558 def dtype(ntv_type, convert=True): 559 ''' return dtype from ntv_type 560 561 *parameters* 562 563 - **convert** : boolean (default True) - if True, dtype if from converted data 564 ''' 565 if not ntv_type: 566 return None 567 if convert: 568 if ntv_type[:8] == 'datetime' and ntv_type[8:]: 569 return 'datetime64' + ntv_type[8:] 570 return Ndtype(ntv_type).dtype 571 return Nutil.CONVERT_DT[Ndtype(ntv_type).json_type] 572 573 @staticmethod 574 def json_ntv(ntv_name, ntv_type, ntv_value, **kwargs): 575 ''' return the JSON representation of a NTV entity 576 577 *parameters* 578 579 - **ntv_name** : string - name of the NTV 580 - **ntv_type** : string - type of the NTV 581 - **ntv_value** : string - Json value of the NTV 582 - **encoded** : boolean (default False) - if True return JsonText else JsonValue 583 - **header** : boolean (default True) - if True include ntv_name + ntv_type 584 ''' 585 name = ntv_name if ntv_name else '' 586 option = {'encoded': False, 'header': True} | kwargs 587 if option['header'] or name: 588 typ = ':' + ntv_type if option['header'] and ntv_type else '' 589 jsn = {name + typ: ntv_value} if name + typ else ntv_value 590 else: 591 jsn = ntv_value 592 if option['encoded']: 593 return json.dumps(jsn) 594 return jsn 595 596 597class NdarrayError(Exception): 598 '''Multidimensional exception'''
29class Ndarray: 30 ''' The Ndarray class is the JSON interface of numpy.ndarrays. 31 32 *static methods* 33 - `read_json` 34 - `to_json` 35 - `set_shape` 36 ''' 37 38 def __init__(self, dar, ntv_type=None, shape=None, str_uri=True): 39 '''Ndarray constructor. 40 41 *Parameters* 42 43 - **dar**: Darray or np.ndarray - data to represent 44 - **shape** : list of integer (default None) - length of dimensions 45 - **ntv_type**: string (default None) - NTVtype to apply 46 - **str_uri**: boolean(default True) - if True and dar is a string, 47 dar is an uri else a np.array 48 ''' 49 dar = [None] if isinstance(dar, list) and len(dar) == 0 else dar 50 if isinstance(dar, Ndarray): 51 self.uri = dar.uri 52 self.is_json = dar.is_json 53 self.ntvtype = dar.ntvtype 54 self.shape = dar.shape 55 self.darray = dar.darray 56 return 57 if isinstance(dar, str) and str_uri: 58 self.uri = dar 59 self.is_json = True 60 self.ntvtype = Ndtype(ntv_type) if ntv_type else None 61 self.shape = shape 62 self.darray = None 63 return 64 if shape: 65 dar = Dfull(dar, dtype=Nutil.dtype(ntv_type), unidim=True).data 66 else: 67 dar = np.array(dar, dtype=Nutil.dtype(ntv_type)) 68 shape = list(dar.shape) 69 dar = np.array(dar).reshape(-1) 70 ntv_type = Nutil.nda_ntv_type(dar, ntv_type) 71 self.uri = None 72 self.is_json = Nutil.is_json(dar[0]) 73 self.ntvtype = Ndtype(ntv_type) 74 self.shape = shape 75 self.darray = dar.astype(Nutil.dtype(str(self.ntvtype))) 76 77 def __repr__(self): 78 '''return classname, the shape and the ntv_type''' 79 uri = self.uri if self.uri else '' 80 typ = self.ntv_type if self.ntv_type else '' 81 sha = str(self.shape) if self.shape else '' 82 u_t = ', ' if uri and typ + sha else '' 83 t_s = ', ' if typ and sha else '' 84 return self.__class__.__name__ + '(' + uri + u_t + typ + t_s + sha + ')' 85 86 def __str__(self): 87 '''return json string format''' 88 return json.dumps(self.to_json()) 89 90 def __eq__(self, other): 91 ''' equal if attributes are equal''' 92 if self.ntv_type != other.ntv_type: 93 return False 94 if self.uri != other.uri: 95 return False 96 if self.shape != other.shape: 97 return False 98 if self.darray is None and other.darray is None: 99 return True 100 if self.darray is None or other.darray is None: 101 return False 102 return Dutil.equals(self.darray, other.darray) 103 104 def __len__(self): 105 ''' len of ndarray''' 106 return len(self.darray) if self.darray is not None else 0 107 108 def __contains__(self, item): 109 ''' item of darray values''' 110 return item in self.darray if self.darray is not None else None 111 112 def __getitem__(self, ind): 113 ''' return darray value item''' 114 if self.darray is None: 115 return None 116 if isinstance(ind, tuple): 117 return [self.darray[i] for i in ind] 118 return self.darray[ind] 119 120 def __copy__(self): 121 ''' Copy all the data ''' 122 return self.__class__(self) 123 124 def __array__(self): 125 '''numpy array interface''' 126 return self.ndarray 127 128 @property 129 def ntv_type(self): 130 ''' string representation of ntvtype''' 131 return str(self.ntvtype) if self.ntvtype else None 132 133 @property 134 def ndarray(self): 135 '''representation with a np.ndarray not flattened''' 136 return self.darray.reshape(self.shape) if self.darray is not None else None 137 138 def set_shape(self, shape): 139 '''update the shape''' 140 if Ndarray.len_shape(shape) != len(self.darray): 141 raise NdarrayError( 142 "shape is not consistent with the ndarray length") 143 self.shape = list(shape) 144 145 def update(self, nda, nda_uri=True): 146 '''update uri and darray and return the result (True, False) 147 148 *Parameters* 149 150 - **nda** : string, list, np.ndarray, Ndarray - data to include 151 - **nda_uri** : boolean (default True) - if True, existing shape and 152 ntv_type are not updated (but are created if not existing)''' 153 if not nda_uri and not (self.shape is None or nda.shape is None 154 ) and self.shape != nda.shape: 155 return False 156 if not nda_uri and not (self.ntv_type is None or nda.ntv_type is None 157 ) and self.ntv_type != nda.ntv_type: 158 return False 159 if nda_uri: 160 len_s = self.len_shape(self.shape) 161 if len_s and len(nda) and len_s != len(nda): 162 return False 163 self.ntvtype = nda.ntvtype if self.ntv_type is None else self.ntvtype 164 self.shape = nda.shape if self.shape is None else self.shape 165 else: 166 self.ntvtype = nda.ntvtype if nda.ntv_type is not None else self.ntvtype 167 self.shape = nda.shape if nda.shape is not None else self.shape 168 self.uri, self.darray = ( 169 nda.uri, None) if nda.uri else (None, nda.darray) 170 return True 171 172 def set_array(self, darray): 173 '''set a new darray and remove uri, return the result (True, False) 174 175 *Parameters* 176 177 - **darray** : list, np.ndarray, Ndarray - data to include''' 178 ndarray = Ndarray(darray) 179 darray = ndarray.darray 180 ntv_type = ndarray.ntv_type 181 shape = ndarray.shape 182 new_shape = shape if self.shape is None else self.shape 183 new_ntv_type = ntv_type if self.ntv_type is None else self.ntv_type 184 if (len(darray) != Ndarray.len_shape(new_shape) or 185 new_ntv_type != ntv_type or new_shape != shape): 186 return False 187 self.uri = None 188 self.darray = darray 189 self.ntvtype = Ndtype(new_ntv_type) 190 self.shape = new_shape 191 return True 192 193 def set_uri(self, uri, no_ntv_type=False, no_shape=False): 194 '''set a new uri and remove ndarray and optionaly ntv_type and shape. 195 Return the result (True, False) 196 197 *Parameters* 198 199 - **uri** : string - URI of the Ndarray 200 - **no_ntv_type** : boolean (default False) - If True, ntv_type is None 201 - **no_shape** : boolean (default False) - If True, shape is None 202 ''' 203 if not isinstance(uri, str) or not uri: 204 return False 205 self.uri = uri 206 self.darray = None 207 self.ntvtype = None if no_ntv_type else self.ntvtype 208 self.shape = None if no_shape else self.shape 209 return True 210 211 def to_ndarray(self): 212 '''representation with a np.ndarray not flattened''' 213 return self.ndarray 214 215 @property 216 def mode(self): 217 '''representation mode of the darray/uri data (relative, absolute, 218 undefined, inconsistent)''' 219 match [self.darray, self.uri]: 220 case [None, str()]: 221 return 'relative' 222 case [None, None]: 223 return 'undefined' 224 case [_, None]: 225 return 'absolute' 226 case _: 227 return 'inconsistent' 228 229 @staticmethod 230 def read_json(jsn, **kwargs): 231 ''' convert json ntv_value into a ndarray. 232 233 *Parameters* 234 235 - **convert** : boolean (default True) - If True, convert json data with 236 non Numpy ntv_type into data with python type 237 ''' 238 option = {'convert': True} | kwargs 239 jso = json.loads(jsn) if isinstance(jsn, str) else jsn 240 ntv_value, = Ntv.decode_json(jso)[:1] 241 242 ntv_type = None 243 shape = None 244 match ntv_value[:-1]: 245 case []: ... 246 case [ntv_type, shape]: ... 247 case [str(ntv_type)]: ... 248 case [list(shape)]: ... 249 unidim = shape is not None 250 if isinstance(ntv_value[-1], str): 251 return Ndarray(ntv_value[-1], shape=shape, ntv_type=ntv_type) 252 darray = Darray.read_json(ntv_value[-1], dtype=Nutil.dtype(ntv_type), 253 unidim=unidim) 254 darray.data = Nutil.convert(ntv_type, darray.data, tojson=False, 255 convert=option['convert']) 256 return Ndarray(darray.values, shape=shape, ntv_type=ntv_type) 257 258 def to_json(self, **kwargs): 259 ''' convert a Ndarray into json-value 260 261 *Parameters* 262 263 - **noshape** : Boolean (default True) - if True, without shape if dim < 1 264 - **notype** : Boolean (default False) - including data type if False 265 - **novalue** : Boolean (default False) - including value if False 266 - **format** : string (default 'full') - representation format of the ndarray, 267 - **encoded** : Boolean (default False) - json-value if False else json-text 268 - **header** : Boolean (default True) - including ndarray type 269 ''' 270 option = {'format': 'full', 'header': True, 'encoded': False, 271 'notype': False, 'noshape': True, 'novalue': False} | kwargs 272 if self.mode in ['undefined', 'inconsistent']: 273 return None 274 if self.mode == 'absolute' and len(self.darray) == 0: 275 return [[]] 276 277 shape = None if not self.shape or (len(self.shape) < 2 and 278 option['noshape']) else self.shape 279 280 if self.mode == 'relative': 281 js_val = self.uri 282 else: 283 js_val = Nutil.ntv_val(self.ntv_type, self.darray, option['format'], 284 self.is_json) if not option['novalue'] else ['-'] 285 286 lis = [self.ntv_type if not option['notype'] else None, shape, js_val] 287 return Nutil.json_ntv(None, 'ndarray', 288 [val for val in lis if val is not None], 289 header=option['header'], encoded=option['encoded']) 290 291 @property 292 def info(self): 293 ''' infos of the Ndarray''' 294 inf = {'shape': self.shape} 295 inf['length'] = len(self) 296 inf['ntvtype'] = self.ntv_type 297 inf['shape'] = self.shape 298 inf['uri'] = self.uri 299 return {key: val for key, val in inf.items() if val} 300 301 @staticmethod 302 def len_shape(shape): 303 '''return a length from a shape (product of dimensions)''' 304 if not shape: 305 return 0 306 prod = 1 307 for dim in shape: 308 prod *= dim 309 return prod
The Ndarray class is the JSON interface of numpy.ndarrays.
static methods
38 def __init__(self, dar, ntv_type=None, shape=None, str_uri=True): 39 '''Ndarray constructor. 40 41 *Parameters* 42 43 - **dar**: Darray or np.ndarray - data to represent 44 - **shape** : list of integer (default None) - length of dimensions 45 - **ntv_type**: string (default None) - NTVtype to apply 46 - **str_uri**: boolean(default True) - if True and dar is a string, 47 dar is an uri else a np.array 48 ''' 49 dar = [None] if isinstance(dar, list) and len(dar) == 0 else dar 50 if isinstance(dar, Ndarray): 51 self.uri = dar.uri 52 self.is_json = dar.is_json 53 self.ntvtype = dar.ntvtype 54 self.shape = dar.shape 55 self.darray = dar.darray 56 return 57 if isinstance(dar, str) and str_uri: 58 self.uri = dar 59 self.is_json = True 60 self.ntvtype = Ndtype(ntv_type) if ntv_type else None 61 self.shape = shape 62 self.darray = None 63 return 64 if shape: 65 dar = Dfull(dar, dtype=Nutil.dtype(ntv_type), unidim=True).data 66 else: 67 dar = np.array(dar, dtype=Nutil.dtype(ntv_type)) 68 shape = list(dar.shape) 69 dar = np.array(dar).reshape(-1) 70 ntv_type = Nutil.nda_ntv_type(dar, ntv_type) 71 self.uri = None 72 self.is_json = Nutil.is_json(dar[0]) 73 self.ntvtype = Ndtype(ntv_type) 74 self.shape = shape 75 self.darray = dar.astype(Nutil.dtype(str(self.ntvtype)))
Ndarray constructor.
Parameters
- dar: Darray or np.ndarray - data to represent
- shape : list of integer (default None) - length of dimensions
- ntv_type: string (default None) - NTVtype to apply
- str_uri: boolean(default True) - if True and dar is a string, dar is an uri else a np.array
128 @property 129 def ntv_type(self): 130 ''' string representation of ntvtype''' 131 return str(self.ntvtype) if self.ntvtype else None
string representation of ntvtype
133 @property 134 def ndarray(self): 135 '''representation with a np.ndarray not flattened''' 136 return self.darray.reshape(self.shape) if self.darray is not None else None
representation with a np.ndarray not flattened
138 def set_shape(self, shape): 139 '''update the shape''' 140 if Ndarray.len_shape(shape) != len(self.darray): 141 raise NdarrayError( 142 "shape is not consistent with the ndarray length") 143 self.shape = list(shape)
update the shape
145 def update(self, nda, nda_uri=True): 146 '''update uri and darray and return the result (True, False) 147 148 *Parameters* 149 150 - **nda** : string, list, np.ndarray, Ndarray - data to include 151 - **nda_uri** : boolean (default True) - if True, existing shape and 152 ntv_type are not updated (but are created if not existing)''' 153 if not nda_uri and not (self.shape is None or nda.shape is None 154 ) and self.shape != nda.shape: 155 return False 156 if not nda_uri and not (self.ntv_type is None or nda.ntv_type is None 157 ) and self.ntv_type != nda.ntv_type: 158 return False 159 if nda_uri: 160 len_s = self.len_shape(self.shape) 161 if len_s and len(nda) and len_s != len(nda): 162 return False 163 self.ntvtype = nda.ntvtype if self.ntv_type is None else self.ntvtype 164 self.shape = nda.shape if self.shape is None else self.shape 165 else: 166 self.ntvtype = nda.ntvtype if nda.ntv_type is not None else self.ntvtype 167 self.shape = nda.shape if nda.shape is not None else self.shape 168 self.uri, self.darray = ( 169 nda.uri, None) if nda.uri else (None, nda.darray) 170 return True
update uri and darray and return the result (True, False)
Parameters
- nda : string, list, np.ndarray, Ndarray - data to include
- nda_uri : boolean (default True) - if True, existing shape and ntv_type are not updated (but are created if not existing)
172 def set_array(self, darray): 173 '''set a new darray and remove uri, return the result (True, False) 174 175 *Parameters* 176 177 - **darray** : list, np.ndarray, Ndarray - data to include''' 178 ndarray = Ndarray(darray) 179 darray = ndarray.darray 180 ntv_type = ndarray.ntv_type 181 shape = ndarray.shape 182 new_shape = shape if self.shape is None else self.shape 183 new_ntv_type = ntv_type if self.ntv_type is None else self.ntv_type 184 if (len(darray) != Ndarray.len_shape(new_shape) or 185 new_ntv_type != ntv_type or new_shape != shape): 186 return False 187 self.uri = None 188 self.darray = darray 189 self.ntvtype = Ndtype(new_ntv_type) 190 self.shape = new_shape 191 return True
set a new darray and remove uri, return the result (True, False)
Parameters
- darray : list, np.ndarray, Ndarray - data to include
193 def set_uri(self, uri, no_ntv_type=False, no_shape=False): 194 '''set a new uri and remove ndarray and optionaly ntv_type and shape. 195 Return the result (True, False) 196 197 *Parameters* 198 199 - **uri** : string - URI of the Ndarray 200 - **no_ntv_type** : boolean (default False) - If True, ntv_type is None 201 - **no_shape** : boolean (default False) - If True, shape is None 202 ''' 203 if not isinstance(uri, str) or not uri: 204 return False 205 self.uri = uri 206 self.darray = None 207 self.ntvtype = None if no_ntv_type else self.ntvtype 208 self.shape = None if no_shape else self.shape 209 return True
set a new uri and remove ndarray and optionaly ntv_type and shape. Return the result (True, False)
Parameters
- uri : string - URI of the Ndarray
- no_ntv_type : boolean (default False) - If True, ntv_type is None
- no_shape : boolean (default False) - If True, shape is None
211 def to_ndarray(self): 212 '''representation with a np.ndarray not flattened''' 213 return self.ndarray
representation with a np.ndarray not flattened
215 @property 216 def mode(self): 217 '''representation mode of the darray/uri data (relative, absolute, 218 undefined, inconsistent)''' 219 match [self.darray, self.uri]: 220 case [None, str()]: 221 return 'relative' 222 case [None, None]: 223 return 'undefined' 224 case [_, None]: 225 return 'absolute' 226 case _: 227 return 'inconsistent'
representation mode of the darray/uri data (relative, absolute, undefined, inconsistent)
229 @staticmethod 230 def read_json(jsn, **kwargs): 231 ''' convert json ntv_value into a ndarray. 232 233 *Parameters* 234 235 - **convert** : boolean (default True) - If True, convert json data with 236 non Numpy ntv_type into data with python type 237 ''' 238 option = {'convert': True} | kwargs 239 jso = json.loads(jsn) if isinstance(jsn, str) else jsn 240 ntv_value, = Ntv.decode_json(jso)[:1] 241 242 ntv_type = None 243 shape = None 244 match ntv_value[:-1]: 245 case []: ... 246 case [ntv_type, shape]: ... 247 case [str(ntv_type)]: ... 248 case [list(shape)]: ... 249 unidim = shape is not None 250 if isinstance(ntv_value[-1], str): 251 return Ndarray(ntv_value[-1], shape=shape, ntv_type=ntv_type) 252 darray = Darray.read_json(ntv_value[-1], dtype=Nutil.dtype(ntv_type), 253 unidim=unidim) 254 darray.data = Nutil.convert(ntv_type, darray.data, tojson=False, 255 convert=option['convert']) 256 return Ndarray(darray.values, shape=shape, ntv_type=ntv_type)
convert json ntv_value into a ndarray.
Parameters
- convert : boolean (default True) - If True, convert json data with non Numpy ntv_type into data with python type
258 def to_json(self, **kwargs): 259 ''' convert a Ndarray into json-value 260 261 *Parameters* 262 263 - **noshape** : Boolean (default True) - if True, without shape if dim < 1 264 - **notype** : Boolean (default False) - including data type if False 265 - **novalue** : Boolean (default False) - including value if False 266 - **format** : string (default 'full') - representation format of the ndarray, 267 - **encoded** : Boolean (default False) - json-value if False else json-text 268 - **header** : Boolean (default True) - including ndarray type 269 ''' 270 option = {'format': 'full', 'header': True, 'encoded': False, 271 'notype': False, 'noshape': True, 'novalue': False} | kwargs 272 if self.mode in ['undefined', 'inconsistent']: 273 return None 274 if self.mode == 'absolute' and len(self.darray) == 0: 275 return [[]] 276 277 shape = None if not self.shape or (len(self.shape) < 2 and 278 option['noshape']) else self.shape 279 280 if self.mode == 'relative': 281 js_val = self.uri 282 else: 283 js_val = Nutil.ntv_val(self.ntv_type, self.darray, option['format'], 284 self.is_json) if not option['novalue'] else ['-'] 285 286 lis = [self.ntv_type if not option['notype'] else None, shape, js_val] 287 return Nutil.json_ntv(None, 'ndarray', 288 [val for val in lis if val is not None], 289 header=option['header'], encoded=option['encoded'])
convert a Ndarray into json-value
Parameters
- noshape : Boolean (default True) - if True, without shape if dim < 1
- notype : Boolean (default False) - including data type if False
- novalue : Boolean (default False) - including value if False
- format : string (default 'full') - representation format of the ndarray,
- encoded : Boolean (default False) - json-value if False else json-text
- header : Boolean (default True) - including ndarray type
291 @property 292 def info(self): 293 ''' infos of the Ndarray''' 294 inf = {'shape': self.shape} 295 inf['length'] = len(self) 296 inf['ntvtype'] = self.ntv_type 297 inf['shape'] = self.shape 298 inf['uri'] = self.uri 299 return {key: val for key, val in inf.items() if val}
infos of the Ndarray
312class Nutil: 313 '''ntv-ndarray utilities. 314 315 *static methods* 316 - `convert` 317 - `is_json` 318 - `ntv_val` 319 - `add_ext` 320 - `split_type` 321 - `ntv_type` 322 - `nda_ntv_type` 323 - `dtype` 324 - `json_ntv` 325 - `split_name` 326 - `split_json_name` 327 328 ''' 329 CONNECTOR_DT = {'field': 'Series', 'tab': 'DataFrame'} 330 PYTHON_DT = {'array': 'list', 'time': 'datetime.time', 331 'object': 'dict', 'null': 'NoneType', 'decimal64': 'Decimal', 332 'ndarray': 'ndarray', 'narray': 'narray'} 333 LOCATION_DT = {'point': 'Point', 334 'line': 'LineString', 'polygon': 'Polygon'} 335 DT_CONNECTOR = {val: key for key, val in CONNECTOR_DT.items()} 336 DT_PYTHON = {val: key for key, val in PYTHON_DT.items()} 337 DT_LOCATION = {val: key for key, val in LOCATION_DT.items()} 338 DT_NTVTYPE = DT_LOCATION | DT_CONNECTOR | DT_PYTHON 339 340 FORMAT_CLS = {'full': Dfull, 'complete': Dcomplete} 341 STRUCT_DT = {'Ntv': 'object', 'NtvSingle': 'object', 'NtvList': 'object'} 342 CONVERT_DT = {'object': 'object', 'array': 'object', 'json': 'object', 343 'number': 'float', 'boolean': 'bool', 'null': 'object', 344 'string': 'str', 'integer': 'int'} 345 346 @staticmethod 347 def is_json(obj): 348 ''' check if obj is a json structure and return True if obj is a json-value 349 350 *Parameters* 351 352 - **obj** : object to check''' 353 if obj is None: 354 return True 355 is_js = NtvConnector.is_json 356 match obj: 357 case str() | int() | float() | bool(): 358 return True 359 case list() | tuple() as obj: 360 if not obj: 361 return True 362 return min(is_js(obj_in) for obj_in in obj) 363 case dict() as obj: 364 if not obj: 365 return True 366 if not min(isinstance(key, str) for key in obj.keys()): 367 return False 368 return min(is_js(obj_in) for obj_in in obj.values()) 369 case _: 370 return False 371 372 @staticmethod 373 def extend_array(arr, til, shap, order): 374 '''return a flattened np.ndarray extended in additional dimensions 375 376 parameters: 377 378 - arr: np.array to extend 379 - til: integer - parameter to apply to np.tile function 380 - shap: list of integer - shape of the array 381 - order: list of integer - order of dimensions to apply 382 ''' 383 old_order = list(range(len(order))) 384 arr_tab = np.tile(arr, til).reshape(shap) 385 return np.moveaxis(arr_tab, old_order, order).flatten() 386 387 @staticmethod 388 def convert(ntv_type, nda, tojson=True, convert=True): 389 ''' convert np.ndarray with external NTVtype. 390 391 *Parameters* 392 393 - **ntv_type** : string - NTVtype deduced from the np.ndarray name_type and dtype, 394 - **nda** : np.ndarray to be converted. 395 - **tojson** : boolean (default True) - apply to json function 396 - **convert** : boolean (default True) - If True, convert json data with 397 non Numpy ntv_type into data with python type 398 ''' 399 400 dtype = Nutil.dtype(ntv_type) 401 jtype = Nutil.dtype(ntv_type, convert=False) 402 if tojson: 403 match ntv_type: 404 case dat if Ndtype(dat).category == 'datation': 405 return nda.astype(dtype).astype(jtype) 406 case 'base16': 407 return nda.astype(dtype) 408 case 'time' | 'decimal64': 409 return nda.astype(jtype) 410 case 'geojson': 411 return np.frompyfunc(ShapelyConnec.to_geojson, 1, 1)(nda) 412 case _: 413 return nda 414 else: 415 match [ntv_type, convert]: 416 case [None, _]: 417 return nda 418 case [_, False]: 419 return nda.astype(jtype) 420 case ['time', _]: 421 return np.frompyfunc(datetime.time.fromisoformat, 1, 1)(nda) 422 case ['decimal64', _]: 423 return np.frompyfunc(Decimal, 1, 1)(nda) 424 case ['narray', _]: 425 nar = np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 426 return np.frompyfunc(Ndarray.to_ndarray, 1, 1)(nar) 427 case ['ndarray', _]: 428 return np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 429 case [('point' | 'line' | 'polygon' | 'geometry'), _]: 430 return np.frompyfunc(ShapelyConnec.to_geometry, 1, 1)(nda) 431 case [connec, _] if connec in Nutil.CONNECTOR_DT: 432 return np.fromiter([NtvConnector.uncast(nd, None, connec)[0] 433 for nd in nda], dtype='object') 434 case _: 435 return nda.astype(dtype) 436 437 # float.fromhex(x.hex()) == x, bytes(bytearray.fromhex(x.hex())) == x 438 @staticmethod 439 def ntv_val(ntv_type, nda, form, is_json=False): 440 ''' convert a np.ndarray into NTV json-value. 441 442 *Parameters* 443 444 - **ntv_type** : string - NTVtype deduced from the ndarray, name_type and dtype, 445 - **nda** : ndarray to be converted. 446 - **form** : format of data ('full', 'complete', 'sparse', 'primary'). 447 - **is_json** : boolean (defaut False) - True if nda data is Json data 448 ''' 449 if form == 'complete' and len(nda) < 2: 450 raise NdarrayError( 451 "complete format is not available with ndarray length < 2") 452 Format = Nutil.FORMAT_CLS[form] 453 darray = Format(nda) 454 ref = darray.ref 455 coding = darray.coding 456 if is_json: 457 return Format(darray.data, ref=ref, coding=coding).to_json() 458 match ntv_type: 459 case 'narray': 460 data = [Ndarray(nd).to_json(header=False) 461 for nd in darray.data] 462 case 'ndarray': 463 data = [Ndarray(nd).to_json(header=False) 464 for nd in darray.data] 465 case connec if connec in Nutil.CONNECTOR_DT: 466 data = [NtvConnector.cast(nd, None, connec)[0] 467 for nd in darray.data] 468 case 'point' | 'line' | 'polygon' | 'geometry': 469 data = np.frompyfunc(ShapelyConnec.to_coord, 1, 1)(darray.data) 470 case None: 471 data = nda 472 case _: 473 data = Nutil.convert(ntv_type, darray.data) 474 return Format(data, ref=ref, coding=coding).to_json() 475 476 @staticmethod 477 def add_ext(typ, ext): 478 '''return extended type string: "typ[ext]"''' 479 ext = '[' + ext + ']' if ext else '' 480 return '' if not typ else typ + ext 481 482 @staticmethod 483 def split_type(typ): 484 '''return a tuple with typ and extension''' 485 if not isinstance(typ, str): 486 return (None, None) 487 spl = typ.split('[', maxsplit=1) 488 return (spl[0], None) if len(spl) == 1 else (spl[0], spl[1][:-1]) 489 490 @staticmethod 491 def split_json_name(string, notnone=False): 492 '''return a tuple with name, ntv_type from string''' 493 null = '' if notnone else None 494 if not string or string == ':': 495 return (null, null) 496 spl = string.rsplit(':', maxsplit=1) 497 if len(spl) == 1: 498 return (string, null) 499 if spl[0] == '': 500 return (null, spl[1]) 501 sp0 = spl[0][:-1] if spl[0][-1] == ':' else spl[0] 502 return (null if sp0 == '' else sp0, null if spl[1] == '' else spl[1]) 503 504 @staticmethod 505 def split_name(string): 506 '''return a list with name, add_name from string''' 507 if not string or string == '.': 508 return ['', ''] 509 spl = string.split('.', maxsplit=1) 510 spl = [spl[0], ''] if len(spl) < 2 else spl 511 return spl 512 513 @staticmethod 514 def ntv_type(dtype, ntv_type=None, ext=None): 515 ''' return ntv_type string from dtype, additional type and extension. 516 517 *Parameters* 518 519 - **dtype** : string - dtype of the ndarray 520 - **ntv_type** : string - additional type 521 - **ext** : string - type extension 522 ''' 523 np_ntype = NP_NTYPE | Nutil.DT_NTVTYPE | { 524 'int': 'int', 'object': 'object'} 525 if ntv_type: 526 return Nutil.add_ext(ntv_type, ext) 527 match dtype: 528 case string if string[:3] == 'str': 529 return Nutil.add_ext('string', ext) 530 case bytesxx if bytesxx[:5] == 'bytes': 531 return Nutil.add_ext('base16', ext) 532 case dtyp if dtyp in np_ntype: 533 return Nutil.add_ext(np_ntype[dtyp], ext) 534 case date if date[:10] == 'datetime64': 535 return 'datetime' + date[10:] 536 case delta if delta[:11] == 'timedelta64': 537 return 'timedelta' + delta[11:] 538 case _: 539 return Nutil.add_ext(dtype, ext) 540 541 @staticmethod 542 def nda_ntv_type(nda, ntv_type=None, ext=None): 543 '''return ntv_type string from an ndarray, additional type and extension. 544 545 *Parameters* 546 547 - **nda** : ndarray - data used to calculate the ntv_type 548 - **ntv_type** : string - additional type 549 - **ext** : string - type extension 550 ''' 551 if ntv_type or nda is None: 552 return ntv_type 553 dtype = nda.dtype.name 554 pytype = nda.flat[0].__class__.__name__ 555 dtype = pytype if dtype == 'object' and pytype not in Nutil.STRUCT_DT else dtype 556 return Nutil.ntv_type(dtype, ntv_type, ext) 557 558 @staticmethod 559 def dtype(ntv_type, convert=True): 560 ''' return dtype from ntv_type 561 562 *parameters* 563 564 - **convert** : boolean (default True) - if True, dtype if from converted data 565 ''' 566 if not ntv_type: 567 return None 568 if convert: 569 if ntv_type[:8] == 'datetime' and ntv_type[8:]: 570 return 'datetime64' + ntv_type[8:] 571 return Ndtype(ntv_type).dtype 572 return Nutil.CONVERT_DT[Ndtype(ntv_type).json_type] 573 574 @staticmethod 575 def json_ntv(ntv_name, ntv_type, ntv_value, **kwargs): 576 ''' return the JSON representation of a NTV entity 577 578 *parameters* 579 580 - **ntv_name** : string - name of the NTV 581 - **ntv_type** : string - type of the NTV 582 - **ntv_value** : string - Json value of the NTV 583 - **encoded** : boolean (default False) - if True return JsonText else JsonValue 584 - **header** : boolean (default True) - if True include ntv_name + ntv_type 585 ''' 586 name = ntv_name if ntv_name else '' 587 option = {'encoded': False, 'header': True} | kwargs 588 if option['header'] or name: 589 typ = ':' + ntv_type if option['header'] and ntv_type else '' 590 jsn = {name + typ: ntv_value} if name + typ else ntv_value 591 else: 592 jsn = ntv_value 593 if option['encoded']: 594 return json.dumps(jsn) 595 return jsn
ntv-ndarray utilities.
static methods
346 @staticmethod 347 def is_json(obj): 348 ''' check if obj is a json structure and return True if obj is a json-value 349 350 *Parameters* 351 352 - **obj** : object to check''' 353 if obj is None: 354 return True 355 is_js = NtvConnector.is_json 356 match obj: 357 case str() | int() | float() | bool(): 358 return True 359 case list() | tuple() as obj: 360 if not obj: 361 return True 362 return min(is_js(obj_in) for obj_in in obj) 363 case dict() as obj: 364 if not obj: 365 return True 366 if not min(isinstance(key, str) for key in obj.keys()): 367 return False 368 return min(is_js(obj_in) for obj_in in obj.values()) 369 case _: 370 return False
check if obj is a json structure and return True if obj is a json-value
Parameters
- obj : object to check
372 @staticmethod 373 def extend_array(arr, til, shap, order): 374 '''return a flattened np.ndarray extended in additional dimensions 375 376 parameters: 377 378 - arr: np.array to extend 379 - til: integer - parameter to apply to np.tile function 380 - shap: list of integer - shape of the array 381 - order: list of integer - order of dimensions to apply 382 ''' 383 old_order = list(range(len(order))) 384 arr_tab = np.tile(arr, til).reshape(shap) 385 return np.moveaxis(arr_tab, old_order, order).flatten()
return a flattened np.ndarray extended in additional dimensions
parameters:
- arr: np.array to extend
- til: integer - parameter to apply to np.tile function
- shap: list of integer - shape of the array
- order: list of integer - order of dimensions to apply
387 @staticmethod 388 def convert(ntv_type, nda, tojson=True, convert=True): 389 ''' convert np.ndarray with external NTVtype. 390 391 *Parameters* 392 393 - **ntv_type** : string - NTVtype deduced from the np.ndarray name_type and dtype, 394 - **nda** : np.ndarray to be converted. 395 - **tojson** : boolean (default True) - apply to json function 396 - **convert** : boolean (default True) - If True, convert json data with 397 non Numpy ntv_type into data with python type 398 ''' 399 400 dtype = Nutil.dtype(ntv_type) 401 jtype = Nutil.dtype(ntv_type, convert=False) 402 if tojson: 403 match ntv_type: 404 case dat if Ndtype(dat).category == 'datation': 405 return nda.astype(dtype).astype(jtype) 406 case 'base16': 407 return nda.astype(dtype) 408 case 'time' | 'decimal64': 409 return nda.astype(jtype) 410 case 'geojson': 411 return np.frompyfunc(ShapelyConnec.to_geojson, 1, 1)(nda) 412 case _: 413 return nda 414 else: 415 match [ntv_type, convert]: 416 case [None, _]: 417 return nda 418 case [_, False]: 419 return nda.astype(jtype) 420 case ['time', _]: 421 return np.frompyfunc(datetime.time.fromisoformat, 1, 1)(nda) 422 case ['decimal64', _]: 423 return np.frompyfunc(Decimal, 1, 1)(nda) 424 case ['narray', _]: 425 nar = np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 426 return np.frompyfunc(Ndarray.to_ndarray, 1, 1)(nar) 427 case ['ndarray', _]: 428 return np.frompyfunc(Ndarray.read_json, 1, 1)(nda) 429 case [('point' | 'line' | 'polygon' | 'geometry'), _]: 430 return np.frompyfunc(ShapelyConnec.to_geometry, 1, 1)(nda) 431 case [connec, _] if connec in Nutil.CONNECTOR_DT: 432 return np.fromiter([NtvConnector.uncast(nd, None, connec)[0] 433 for nd in nda], dtype='object') 434 case _: 435 return nda.astype(dtype) 436 437 # float.fromhex(x.hex()) == x, bytes(bytearray.fromhex(x.hex())) == x
convert np.ndarray with external NTVtype.
Parameters
- ntv_type : string - NTVtype deduced from the np.ndarray name_type and dtype,
- nda : np.ndarray to be converted.
- tojson : boolean (default True) - apply to json function
- convert : boolean (default True) - If True, convert json data with non Numpy ntv_type into data with python type
438 @staticmethod 439 def ntv_val(ntv_type, nda, form, is_json=False): 440 ''' convert a np.ndarray into NTV json-value. 441 442 *Parameters* 443 444 - **ntv_type** : string - NTVtype deduced from the ndarray, name_type and dtype, 445 - **nda** : ndarray to be converted. 446 - **form** : format of data ('full', 'complete', 'sparse', 'primary'). 447 - **is_json** : boolean (defaut False) - True if nda data is Json data 448 ''' 449 if form == 'complete' and len(nda) < 2: 450 raise NdarrayError( 451 "complete format is not available with ndarray length < 2") 452 Format = Nutil.FORMAT_CLS[form] 453 darray = Format(nda) 454 ref = darray.ref 455 coding = darray.coding 456 if is_json: 457 return Format(darray.data, ref=ref, coding=coding).to_json() 458 match ntv_type: 459 case 'narray': 460 data = [Ndarray(nd).to_json(header=False) 461 for nd in darray.data] 462 case 'ndarray': 463 data = [Ndarray(nd).to_json(header=False) 464 for nd in darray.data] 465 case connec if connec in Nutil.CONNECTOR_DT: 466 data = [NtvConnector.cast(nd, None, connec)[0] 467 for nd in darray.data] 468 case 'point' | 'line' | 'polygon' | 'geometry': 469 data = np.frompyfunc(ShapelyConnec.to_coord, 1, 1)(darray.data) 470 case None: 471 data = nda 472 case _: 473 data = Nutil.convert(ntv_type, darray.data) 474 return Format(data, ref=ref, coding=coding).to_json()
convert a np.ndarray into NTV json-value.
Parameters
- ntv_type : string - NTVtype deduced from the ndarray, name_type and dtype,
- nda : ndarray to be converted.
- form : format of data ('full', 'complete', 'sparse', 'primary').
- is_json : boolean (defaut False) - True if nda data is Json data
476 @staticmethod 477 def add_ext(typ, ext): 478 '''return extended type string: "typ[ext]"''' 479 ext = '[' + ext + ']' if ext else '' 480 return '' if not typ else typ + ext
return extended type string: "typ[ext]"
482 @staticmethod 483 def split_type(typ): 484 '''return a tuple with typ and extension''' 485 if not isinstance(typ, str): 486 return (None, None) 487 spl = typ.split('[', maxsplit=1) 488 return (spl[0], None) if len(spl) == 1 else (spl[0], spl[1][:-1])
return a tuple with typ and extension
490 @staticmethod 491 def split_json_name(string, notnone=False): 492 '''return a tuple with name, ntv_type from string''' 493 null = '' if notnone else None 494 if not string or string == ':': 495 return (null, null) 496 spl = string.rsplit(':', maxsplit=1) 497 if len(spl) == 1: 498 return (string, null) 499 if spl[0] == '': 500 return (null, spl[1]) 501 sp0 = spl[0][:-1] if spl[0][-1] == ':' else spl[0] 502 return (null if sp0 == '' else sp0, null if spl[1] == '' else spl[1])
return a tuple with name, ntv_type from string
504 @staticmethod 505 def split_name(string): 506 '''return a list with name, add_name from string''' 507 if not string or string == '.': 508 return ['', ''] 509 spl = string.split('.', maxsplit=1) 510 spl = [spl[0], ''] if len(spl) < 2 else spl 511 return spl
return a list with name, add_name from string
513 @staticmethod 514 def ntv_type(dtype, ntv_type=None, ext=None): 515 ''' return ntv_type string from dtype, additional type and extension. 516 517 *Parameters* 518 519 - **dtype** : string - dtype of the ndarray 520 - **ntv_type** : string - additional type 521 - **ext** : string - type extension 522 ''' 523 np_ntype = NP_NTYPE | Nutil.DT_NTVTYPE | { 524 'int': 'int', 'object': 'object'} 525 if ntv_type: 526 return Nutil.add_ext(ntv_type, ext) 527 match dtype: 528 case string if string[:3] == 'str': 529 return Nutil.add_ext('string', ext) 530 case bytesxx if bytesxx[:5] == 'bytes': 531 return Nutil.add_ext('base16', ext) 532 case dtyp if dtyp in np_ntype: 533 return Nutil.add_ext(np_ntype[dtyp], ext) 534 case date if date[:10] == 'datetime64': 535 return 'datetime' + date[10:] 536 case delta if delta[:11] == 'timedelta64': 537 return 'timedelta' + delta[11:] 538 case _: 539 return Nutil.add_ext(dtype, ext)
return ntv_type string from dtype, additional type and extension.
Parameters
- dtype : string - dtype of the ndarray
- ntv_type : string - additional type
- ext : string - type extension
541 @staticmethod 542 def nda_ntv_type(nda, ntv_type=None, ext=None): 543 '''return ntv_type string from an ndarray, additional type and extension. 544 545 *Parameters* 546 547 - **nda** : ndarray - data used to calculate the ntv_type 548 - **ntv_type** : string - additional type 549 - **ext** : string - type extension 550 ''' 551 if ntv_type or nda is None: 552 return ntv_type 553 dtype = nda.dtype.name 554 pytype = nda.flat[0].__class__.__name__ 555 dtype = pytype if dtype == 'object' and pytype not in Nutil.STRUCT_DT else dtype 556 return Nutil.ntv_type(dtype, ntv_type, ext)
return ntv_type string from an ndarray, additional type and extension.
Parameters
- nda : ndarray - data used to calculate the ntv_type
- ntv_type : string - additional type
- ext : string - type extension
558 @staticmethod 559 def dtype(ntv_type, convert=True): 560 ''' return dtype from ntv_type 561 562 *parameters* 563 564 - **convert** : boolean (default True) - if True, dtype if from converted data 565 ''' 566 if not ntv_type: 567 return None 568 if convert: 569 if ntv_type[:8] == 'datetime' and ntv_type[8:]: 570 return 'datetime64' + ntv_type[8:] 571 return Ndtype(ntv_type).dtype 572 return Nutil.CONVERT_DT[Ndtype(ntv_type).json_type]
return dtype from ntv_type
parameters
- convert : boolean (default True) - if True, dtype if from converted data
574 @staticmethod 575 def json_ntv(ntv_name, ntv_type, ntv_value, **kwargs): 576 ''' return the JSON representation of a NTV entity 577 578 *parameters* 579 580 - **ntv_name** : string - name of the NTV 581 - **ntv_type** : string - type of the NTV 582 - **ntv_value** : string - Json value of the NTV 583 - **encoded** : boolean (default False) - if True return JsonText else JsonValue 584 - **header** : boolean (default True) - if True include ntv_name + ntv_type 585 ''' 586 name = ntv_name if ntv_name else '' 587 option = {'encoded': False, 'header': True} | kwargs 588 if option['header'] or name: 589 typ = ':' + ntv_type if option['header'] and ntv_type else '' 590 jsn = {name + typ: ntv_value} if name + typ else ntv_value 591 else: 592 jsn = ntv_value 593 if option['encoded']: 594 return json.dumps(jsn) 595 return jsn
return the JSON representation of a NTV entity
parameters
- ntv_name : string - name of the NTV
- ntv_type : string - type of the NTV
- ntv_value : string - Json value of the NTV
- encoded : boolean (default False) - if True return JsonText else JsonValue
- header : boolean (default True) - if True include ntv_name + ntv_type
Multidimensional exception
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
- add_note
- args