kisa-utils 0.41.0__py3-none-any.whl → 0.42.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
kisa_utils/db.py CHANGED
@@ -56,6 +56,7 @@ TRIGGERS:dict = {
56
56
  }
57
57
 
58
58
  JSON_SEPARATOR = '::'
59
+ JSON_ARRAY_APPEND_SYMBOL = '+' # arr[+]=N => arr.append(N)
59
60
 
60
61
  RAM_DB_PATHS:list[str] = [':memory:', ':ram:',':RAM:']
61
62
 
@@ -508,24 +509,116 @@ class Api:
508
509
 
509
510
  return tables
510
511
 
511
- def __formRootPathFields(self, root:dict, keys:list, value:Any):
512
+ def __getJSONListIndices(self, jsonPathSection:str) -> Response:
513
+ '''
514
+ get key and indices of a json path
515
+ Returns:
516
+ Response.data ->
517
+ ```
518
+ {
519
+ 'key': str, # if array is a key in a dict, then this is set, otherwise its an empty string
520
+ 'indices': list[int] # contains all indices
521
+ }
522
+ ```
523
+ '''
524
+ data = KDict({
525
+ 'key': '',
526
+ 'indices': [],
527
+ })
528
+
529
+ if '[' not in jsonPathSection:
530
+ return Ok(data)
531
+
532
+ if jsonPathSection.count('[') != jsonPathSection.count(']') or jsonPathSection.index('[') > jsonPathSection.index(']'):
533
+ return Error(f'malformed array indexing found: `{jsonPathSection}`')
534
+
535
+ targetIndex = jsonPathSection.index('[')
536
+ data.key = jsonPathSection[:targetIndex].strip()
537
+
538
+ section = jsonPathSection[targetIndex:]
539
+ while '[' in section:
540
+ if section.count('[') != section.count(']') or section.index('[') > section.index(']'):
541
+ return Error(f'malformed array indexing found: `{jsonPathSection}`')
542
+
543
+ startIndex, endIndex = section.index('['), section.index(']')
544
+
545
+ try:
546
+ index = section[startIndex+1:endIndex]
547
+ index = int(index)
548
+ except:
549
+ index = index.strip()
550
+ if index != JSON_ARRAY_APPEND_SYMBOL:
551
+ return Error(f'malformed array index found: `{jsonPathSection}`->`{section[startIndex+1:endIndex]}`')
552
+
553
+ if JSON_ARRAY_APPEND_SYMBOL in data.indices:
554
+ return Error(f'index `{JSON_ARRAY_APPEND_SYMBOL}` should be the last index: `{jsonPathSection}`->`{section[startIndex+1:endIndex]}`')
555
+
556
+ data.indices.append(index)
557
+
558
+ section = section[endIndex+1:]
559
+
560
+ return Ok(data)
561
+
562
+ def __formRootPathFields(self, root:dict|list, keys:list, value:Any) -> Response:
512
563
  for key in keys[:-1]:
564
+ if not (resp := self.__getJSONListIndices(key)):
565
+ return resp
513
566
 
514
- if key.endswith("[-1]"):
515
- key = key[:-4]
516
- root = root.setdefault(key, [])
517
- else:
567
+ if not resp.data.indices: # dict object
518
568
  root = root.setdefault(key, {})
569
+ else: # list object
570
+ if resp.data.key and isinstance(root, dict):
571
+ root = root.setdefault(resp.data.key, [])
572
+
573
+ if not isinstance(root, list):
574
+ return Error(f'indexing non-array: `{key}`')
575
+
576
+ while resp.data.indices:
577
+ _index = index = resp.data.indices.pop(0)
578
+ if _index == JSON_ARRAY_APPEND_SYMBOL:
579
+ return Error(f'append index `{_index}` should be at the end: `{key}`, index `{_index}({index})`')
580
+ if index < 0:
581
+ index = len(root) + index
582
+
583
+ if not (0<= index < len(root)):
584
+ return Error(f'array-index out of bounds: `{key}`, index `{_index}({index})`')
519
585
 
520
- if keys[-1].endswith("[-1]"):
586
+ root = root[index]
521
587
 
522
- key = keys[-1][:-4]
523
- if key not in root:
524
- root[key] = []
525
-
526
- root[key].append(value)
527
- else:
528
- root[keys[-1]] = value
588
+ key = keys[-1]
589
+
590
+ if not (resp := self.__getJSONListIndices(key)):
591
+ return resp
592
+
593
+ if not resp.data.indices: # dict object
594
+ root[key] = value
595
+ else: # list object
596
+ if resp.data.key and isinstance(root, dict):
597
+ root = root.setdefault(resp.data.key, [])
598
+
599
+ if not isinstance(root, list):
600
+ return Error(f'indexing non-array: `{key}`')
601
+
602
+ while len(resp.data.indices):
603
+ _index = index = resp.data.indices.pop(0)
604
+ if _index != JSON_ARRAY_APPEND_SYMBOL:
605
+ if index < 0:
606
+ index = len(root) + index
607
+
608
+ if not (0<= index < len(root)):
609
+ return Error(f'array-index out of bounds: `{key}`, index `{_index}({index})`')
610
+ elif resp.data.indices:
611
+ return Error(f'append index `{_index}` should be at the end: `{key}`, index `{_index}({index})`')
612
+
613
+ if resp.data.indices:
614
+ root = root[index]
615
+ else:
616
+ if _index != JSON_ARRAY_APPEND_SYMBOL:
617
+ root[index] = value
618
+ else:
619
+ root.append(value)
620
+
621
+ return Ok()
529
622
 
530
623
  def __getRootAndPath(self, string, separator:str=JSON_SEPARATOR):
531
624
  index = string.index(separator)
@@ -857,6 +950,7 @@ class Api:
857
950
 
858
951
  rootResult = rootResult[0]
859
952
  rootResult = storage.decodeJSON(rootResult)
953
+ # print('>>> ',rootResult,'<<<')
860
954
 
861
955
  if isinstance(rootResult, list):
862
956
  if self.__transactionMode: self.__transactionData['update']['failed'] += 1
@@ -868,7 +962,9 @@ class Api:
868
962
 
869
963
  json_roots[root] = rootResult
870
964
 
871
- self.__formRootPathFields(json_roots[root], path.split(JSON_SEPARATOR), columnData[index])
965
+ if not (resp := self.__formRootPathFields(json_roots[root], path.split(JSON_SEPARATOR), columnData[index])):
966
+ reply['log'] = resp.log
967
+ return reply if not self.__returnKISAResponse else resp
872
968
 
873
969
  _columns.append(f'{root}=?')
874
970
 
@@ -876,6 +972,7 @@ class Api:
876
972
  columns = _columns
877
973
  values += conditionData
878
974
 
975
+
879
976
  if not condition:
880
977
  if self.__transactionMode: self.__transactionData['update']['failed'] += 1
881
978
  reply['log'] = 'please provide an update condition. use `1` if you want all data updated'
@@ -0,0 +1,2 @@
1
+ from . import callables
2
+ from . import persistent
@@ -0,0 +1,96 @@
1
+ from kisa_utils.db import Handle
2
+ from kisa_utils.queues.callables import queueCallsInThreads
3
+ from kisa_utils.response import Response, Error, Ok
4
+ from kisa_utils.dataStructures import KDict
5
+ from kisa_utils.storage import Path
6
+
7
+ class __PersistentQueueSingleton(type):
8
+ _instances = {}
9
+
10
+ def __call__(cls, *args, **kwargs):
11
+ if not args:
12
+ raise Exception(f'invalid instantiation of {cls}')
13
+ id = args[0]
14
+
15
+ if not id.isalnum():
16
+ raise ValueError('persistent queue ID should be an alphanumeric value ie a-zA-Z0-9 with no special characters or spaces')
17
+
18
+ if id not in cls._instances:
19
+ cls._instances[id] = super().__call__(*args, **kwargs)
20
+
21
+ return cls._instances[id]
22
+
23
+
24
+ class PersistentQueue(metaclass=__PersistentQueueSingleton):
25
+ __openedQueues:dict = {} # name: PersistentQueue
26
+
27
+ __allowedDataTypes = str|int|float|dict|KDict|list|tuple|set
28
+
29
+ __defaultDirectoryName:str = '.kisaPersistentQueues'
30
+ __defaultStorageLocation:str = Path.join(Path.HOME, __defaultDirectoryName)
31
+
32
+ def __init__(self, id:str, /, *, storageLocation:str=''):
33
+ '''
34
+ create a new thread-safe PersistentQueue
35
+ Args:
36
+ id(str): the Queue ID. queue sharing an ID will all reference the same underwlying queue
37
+ storageLocation(str): if provided, the location to store the queue. if not provided, data is stored in the default location. use `obj.defaultStorageLocation` to get the default storage directory
38
+ '''
39
+
40
+ if storageLocation:
41
+ if not Path.exists(storageLocation):
42
+ raise Exception(f'could not find storage directory: `{storageLocation}`')
43
+ else:
44
+ storageLocation = self.__defaultStorageLocation
45
+ if not Path.exists(storageLocation) and not Path.createDirectory(storageLocation):
46
+ raise Exception(f'failed to create storage directory: `{storageLocation}`')
47
+
48
+ self.__length:int = 0
49
+ self.id = id
50
+
51
+ # ensure thread safety throughout the running program
52
+ self.append = queueCallsInThreads(self.append, group=self.id)
53
+ self.peek = queueCallsInThreads(self.peek, group=self.id)
54
+ self.pop = queueCallsInThreads(self.pop, group=self.id)
55
+
56
+
57
+ @property
58
+ def defaultStorageLocation(self):
59
+ '''
60
+ get the default storage location
61
+ '''
62
+ return self.__defaultStorageLocation
63
+
64
+ @property
65
+ def allowedDataTypes(self):
66
+ '''
67
+ get data types allowed in the queue
68
+ '''
69
+ return self.__allowedDataTypes
70
+
71
+ @property
72
+ def length(self) -> int:
73
+ '''
74
+ get number of items in the queue
75
+ '''
76
+ return self.__length
77
+
78
+ def append(self, data:__allowedDataTypes, /) -> Response:
79
+ '''
80
+ append to the queue
81
+ Args:
82
+ data(__allowedDataTypes): the data to insert, use `obj.allowedDataTypes` to get the list of allowed data types
83
+ '''
84
+ ...
85
+
86
+ def peek(self, index:int, /) -> Response:
87
+ '''
88
+ get data at `index` without popping it from the queue
89
+ '''
90
+ ...
91
+
92
+ def pop(self, /, *, index:int = 0) -> Response:
93
+ '''
94
+ get data at `index` and remove it from the queue
95
+ '''
96
+ ...
kisa_utils/storage.py CHANGED
@@ -5,7 +5,15 @@ from kisa_utils.response import Response, Error, Ok
5
5
  encodeJSON = json.JSONEncoder().encode
6
6
  decodeJSON = json.JSONDecoder().decode
7
7
 
8
- class Path:
8
+ class _Meta(type):
9
+ @property
10
+ def HOME(cls):
11
+ '''
12
+ get user's home directory
13
+ '''
14
+ return os.path.expanduser('~')
15
+
16
+ class Path(metaclass=_Meta):
9
17
  join = os.path.join
10
18
  directoryName = os.path.dirname
11
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kisa-utils
3
- Version: 0.41.0
3
+ Version: 0.42.0
4
4
  Summary: Utility functions and modules for KISA Developers
5
5
  Author: Tom Bukenya
6
6
  Author-email: glayn2bukman@gmail.com
@@ -4,7 +4,7 @@ kisa_utils/codes.py,sha256=PV_S53Skggf4XetOdYoIKtEmM8cpN5wZwUlxje70WZY,904
4
4
  kisa_utils/config.py,sha256=NfluzGKTh66qfNtC-Ae0zNb1XzMTgU2Me9Vi82R9c1E,2285
5
5
  kisa_utils/dataStructures.py,sha256=ZgLpttJ66jfpU1NWzLDD1Czqxzj6sWereffgTQWhlV8,2679
6
6
  kisa_utils/dates.py,sha256=zxe4n0PdKReZjK5ZkvnCZtJ55lk5oqu9oS8VX_nLozw,13966
7
- kisa_utils/db.py,sha256=oUpqpew3a1a69Ow1xSydfB1S_QhiUrCtba_7dbK0XBs,49715
7
+ kisa_utils/db.py,sha256=cYlUXDkVCTotsa4pFmoYFkrwPHDEYYeSWJVgIHYRhFQ,53792
8
8
  kisa_utils/encryption.py,sha256=nFzNpzWV_D9uSEq4FsgCnlS7FQtqWP9fvM_81rsfcLo,4218
9
9
  kisa_utils/figures.py,sha256=pYIpQzu1OXRSsY1d98GhgPifnIRmgl-r7S32ai-Ms0c,3731
10
10
  kisa_utils/functionUtils.py,sha256=PlXjnmU1uJWNdISlJJ3SCgavTsgNBoebaa9dtWSFhRA,6553
@@ -12,12 +12,13 @@ kisa_utils/log.py,sha256=0TYdxcIBts026RCSuVIQBcZ-CW1ES7n3M1nEIjmeLTM,2295
12
12
  kisa_utils/remote.py,sha256=0RDrfC4RUW4m6JLziC0_EXJYqzWp38Rw8NDroJ0MuqI,2149
13
13
  kisa_utils/response.py,sha256=asETUBkeF5OlSTwa-coa7lZDCKmQlHCmHf6eaZFl8CU,4560
14
14
  kisa_utils/standardize.py,sha256=nt-uzHQFoKxGscD_MpDYXw65Teg3724whAqa6Kh_zhE,2231
15
- kisa_utils/storage.py,sha256=waHLmrf19QxvEQZlWlC2qQvEYFFhDeJHc7MMNbcmj7c,5823
15
+ kisa_utils/storage.py,sha256=6NdEVrHMS7WB_vmCwiGigIinu-EjxalFJhk1kj-_vWs,5990
16
16
  kisa_utils/threads.py,sha256=qQqsf64YHMyLpboq5AEXKxYqf3iXUhxiJe6Ymg-vlxI,12840
17
17
  kisa_utils/token.py,sha256=Y2qglWYWpmHxoXBh-TH0r1as0uPV5LLqMNcunLvM4vM,7850
18
18
  kisa_utils/permissions/__config__.py,sha256=i3ELkOydDnjKx2ozQTxLZdZ8DXSeUncnl2kRxANjFmM,613
19
19
  kisa_utils/permissions/__init__.py,sha256=q7LGl26f-MPXkLS6nxBKDotW3xdB8y7pI5S_Oo5fPOw,47976
20
- kisa_utils/queues/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ kisa_utils/queues/__init__.py,sha256=VvhceyN5qeiMel1JFQwLRuVk48oBXaWvDtriCubDOms,48
21
+ kisa_utils/queues/persistent.py,sha256=FTIdz1iPrBnICZ3XYuRDtfXonZP83GuCpsNkPUQi2Lg,3269
21
22
  kisa_utils/queues/callables/__init__.py,sha256=OJL3AQnaAS1Eek4H6WBH3WefA2wf-x03cwFmRSK8hoU,141
22
23
  kisa_utils/queues/callables/enqueueFunctionCalls.py,sha256=VIliaMvw4MUdOqts0dXdZCYNxs-QrOVjIRAR3scGrRM,11786
23
24
  kisa_utils/queues/callables/executorQueues.py,sha256=x6bAqxBSZRZ_kL8CK1lSN6JYAYFLxzM84LC1RmwaOLw,6626
@@ -26,7 +27,7 @@ kisa_utils/servers/flask.py,sha256=XZYY1pWnP1mSvaS5Uv8G3EFJV5BJBQtU2gDbO8suvLc,4
26
27
  kisa_utils/structures/__init__.py,sha256=JBU1j3A42jQ62ALKnsS1Hav9YXcYwjDw1wQJtohXPbU,83
27
28
  kisa_utils/structures/utils.py,sha256=665rXIapGwFqejizeJwy3DryeskCQOdgP25BCdLkGvk,2898
28
29
  kisa_utils/structures/validator.py,sha256=JhD9jcfbjTwBr_7OfuNaJd_cYr7wR2emFhsCEo5MCHQ,4323
29
- kisa_utils-0.41.0.dist-info/METADATA,sha256=rtGcPNxF8RSBW5avDpByGXLIuquskvSJ0n9Ij84-EqM,477
30
- kisa_utils-0.41.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
31
- kisa_utils-0.41.0.dist-info/top_level.txt,sha256=GFOLXZYqpBG9xtscGa2uGJAEiZ5NwsqHBH9NylnB29M,11
32
- kisa_utils-0.41.0.dist-info/RECORD,,
30
+ kisa_utils-0.42.0.dist-info/METADATA,sha256=_yhF_JWGl4md4JW9DhH26RPDUoi4LDcSwDw4yez2yfg,477
31
+ kisa_utils-0.42.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
32
+ kisa_utils-0.42.0.dist-info/top_level.txt,sha256=GFOLXZYqpBG9xtscGa2uGJAEiZ5NwsqHBH9NylnB29M,11
33
+ kisa_utils-0.42.0.dist-info/RECORD,,