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 +111 -14
- kisa_utils/queues/__init__.py +2 -0
- kisa_utils/queues/persistent.py +96 -0
- kisa_utils/storage.py +9 -1
- {kisa_utils-0.41.0.dist-info → kisa_utils-0.42.0.dist-info}/METADATA +1 -1
- {kisa_utils-0.41.0.dist-info → kisa_utils-0.42.0.dist-info}/RECORD +8 -7
- {kisa_utils-0.41.0.dist-info → kisa_utils-0.42.0.dist-info}/WHEEL +0 -0
- {kisa_utils-0.41.0.dist-info → kisa_utils-0.42.0.dist-info}/top_level.txt +0 -0
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
|
|
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
|
|
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
|
-
|
|
586
|
+
root = root[index]
|
|
521
587
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
root[
|
|
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'
|
kisa_utils/queues/__init__.py
CHANGED
|
@@ -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
|
|
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
|
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
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.
|
|
30
|
-
kisa_utils-0.
|
|
31
|
-
kisa_utils-0.
|
|
32
|
-
kisa_utils-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|