kisa-utils 0.42.0__py3-none-any.whl → 0.42.2__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 +14 -3
- kisa_utils/queues/persistent.py +117 -7
- {kisa_utils-0.42.0.dist-info → kisa_utils-0.42.2.dist-info}/METADATA +1 -1
- {kisa_utils-0.42.0.dist-info → kisa_utils-0.42.2.dist-info}/RECORD +6 -6
- {kisa_utils-0.42.0.dist-info → kisa_utils-0.42.2.dist-info}/WHEEL +0 -0
- {kisa_utils-0.42.0.dist-info → kisa_utils-0.42.2.dist-info}/top_level.txt +0 -0
kisa_utils/db.py
CHANGED
|
@@ -20,7 +20,8 @@ if sqlite3.sqlite_version_info[1]<38:
|
|
|
20
20
|
sys.exit(f'we need sqlite3 v3.38.0+ to run this program. current version::{sqlite3.sqlite_version}')
|
|
21
21
|
|
|
22
22
|
MAX_FETCH_ITEMS = 16*1024
|
|
23
|
-
RETURN_KISA_RESPONSES = False # ensure all Handles return KISA-Responses globally
|
|
23
|
+
RETURN_KISA_RESPONSES:bool = False # ensure all Handles return KISA-Responses globally
|
|
24
|
+
PARSE_DICTS_TO_KDICTS:bool = True # ensure all dicts parsed on return data are KDicts
|
|
24
25
|
|
|
25
26
|
__EXT__ = 'sqlite3'
|
|
26
27
|
|
|
@@ -710,7 +711,8 @@ class Api:
|
|
|
710
711
|
limit:int=MAX_FETCH_ITEMS, returnDicts:bool=False,
|
|
711
712
|
returnNamespaces:bool=False, parseJson:bool=False,
|
|
712
713
|
returnGenerator:bool=False,
|
|
713
|
-
useKDicts:bool=
|
|
714
|
+
useKDicts:bool|None=None,
|
|
715
|
+
offset:int = 0,
|
|
714
716
|
) -> list|Response:
|
|
715
717
|
'''
|
|
716
718
|
attempt to fetch from the database
|
|
@@ -727,6 +729,7 @@ class Api:
|
|
|
727
729
|
parseJson(bool):if `True`, we shall parse json objects to python lists and dictionaries where possible
|
|
728
730
|
returnGenerator(bool): if True, a generator will be returned instead of the list of tuple|dict|SimpleNamespace. this is especially recommended for large data
|
|
729
731
|
useKDicts(bool): if True, all dicts returned are `KDicts` ie dicts that support the dot notation, just like JS objects
|
|
732
|
+
offset(int): how many rows to skip. along with `limit`, this allows pagination of results
|
|
730
733
|
'''
|
|
731
734
|
if not (limit>0 and limit<=MAX_FETCH_ITEMS):
|
|
732
735
|
err = f'please set a limit on the returned rows. maximum should be {MAX_FETCH_ITEMS}'
|
|
@@ -734,6 +737,14 @@ class Api:
|
|
|
734
737
|
raise ValueError(err)
|
|
735
738
|
return Error(err)
|
|
736
739
|
|
|
740
|
+
if not isinstance(offset, int) or offset < 0:
|
|
741
|
+
err = f'invalid offset given. expected int >= 0'
|
|
742
|
+
if not self.__returnKISAResponse:
|
|
743
|
+
raise ValueError(err)
|
|
744
|
+
return Error(err)
|
|
745
|
+
|
|
746
|
+
useKDicts = PARSE_DICTS_TO_KDICTS if None==useKDicts else useKDicts
|
|
747
|
+
|
|
737
748
|
condition = condition.strip() or '1'
|
|
738
749
|
if not len(condition.strip()):
|
|
739
750
|
err = 'no condition provided'
|
|
@@ -753,7 +764,7 @@ class Api:
|
|
|
753
764
|
# columns = [self.__formatDBJson(_) for _ in columns]
|
|
754
765
|
columns = [self.__formatJSONCondition(_) for _ in columns]
|
|
755
766
|
|
|
756
|
-
__SQL_stmt = f"select {','.join(columns)} from {table} where {condition} limit {limit}"
|
|
767
|
+
__SQL_stmt = f"select {','.join(columns)} from {table} where {condition} limit {limit} offset {offset}"
|
|
757
768
|
# print(f'<{__SQL_stmt}>')
|
|
758
769
|
|
|
759
770
|
if not self.__returnKISAResponse:
|
kisa_utils/queues/persistent.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
from kisa_utils.db import Handle
|
|
2
|
+
from kisa_utils import db
|
|
2
3
|
from kisa_utils.queues.callables import queueCallsInThreads
|
|
3
4
|
from kisa_utils.response import Response, Error, Ok
|
|
4
5
|
from kisa_utils.dataStructures import KDict
|
|
5
6
|
from kisa_utils.storage import Path
|
|
7
|
+
from kisa_utils import dates
|
|
8
|
+
from kisa_utils.functionUtils import enforceRequirements
|
|
9
|
+
from kisa_utils.structures.utils import Value
|
|
10
|
+
|
|
11
|
+
db.RETURN_KISA_RESPONSES = True
|
|
6
12
|
|
|
7
13
|
class __PersistentQueueSingleton(type):
|
|
8
14
|
_instances = {}
|
|
@@ -11,14 +17,17 @@ class __PersistentQueueSingleton(type):
|
|
|
11
17
|
if not args:
|
|
12
18
|
raise Exception(f'invalid instantiation of {cls}')
|
|
13
19
|
id = args[0]
|
|
20
|
+
storageLocation = kwargs.get('storageLocation', '')
|
|
14
21
|
|
|
15
22
|
if not id.isalnum():
|
|
16
23
|
raise ValueError('persistent queue ID should be an alphanumeric value ie a-zA-Z0-9 with no special characters or spaces')
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
idKey = f'{storageLocation}:{id}'
|
|
26
|
+
|
|
27
|
+
if idKey not in cls._instances:
|
|
28
|
+
cls._instances[idKey] = super().__call__(*args, **kwargs)
|
|
20
29
|
|
|
21
|
-
return cls._instances[
|
|
30
|
+
return cls._instances[idKey]
|
|
22
31
|
|
|
23
32
|
|
|
24
33
|
class PersistentQueue(metaclass=__PersistentQueueSingleton):
|
|
@@ -26,9 +35,29 @@ class PersistentQueue(metaclass=__PersistentQueueSingleton):
|
|
|
26
35
|
|
|
27
36
|
__allowedDataTypes = str|int|float|dict|KDict|list|tuple|set
|
|
28
37
|
|
|
38
|
+
__dataTypeCasts = {
|
|
39
|
+
'str': str,
|
|
40
|
+
'int': int,
|
|
41
|
+
'float': float,
|
|
42
|
+
'dict': dict,
|
|
43
|
+
'KDict': KDict,
|
|
44
|
+
'list': list,
|
|
45
|
+
'tuple': tuple,
|
|
46
|
+
'set': set
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
__defaultDirectoryName:str = '.kisaPersistentQueues'
|
|
30
50
|
__defaultStorageLocation:str = Path.join(Path.HOME, __defaultDirectoryName)
|
|
31
51
|
|
|
52
|
+
__schema = {
|
|
53
|
+
'data': '''
|
|
54
|
+
tstamp varchar(30) not null,
|
|
55
|
+
dataType varcahr(30) not null,
|
|
56
|
+
data json not null
|
|
57
|
+
''',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
32
61
|
def __init__(self, id:str, /, *, storageLocation:str=''):
|
|
33
62
|
'''
|
|
34
63
|
create a new thread-safe PersistentQueue
|
|
@@ -48,11 +77,17 @@ class PersistentQueue(metaclass=__PersistentQueueSingleton):
|
|
|
48
77
|
self.__length:int = 0
|
|
49
78
|
self.id = id
|
|
50
79
|
|
|
80
|
+
self.dbPath = Path.join(storageLocation, self.id)
|
|
81
|
+
|
|
51
82
|
# ensure thread safety throughout the running program
|
|
52
83
|
self.append = queueCallsInThreads(self.append, group=self.id)
|
|
53
84
|
self.peek = queueCallsInThreads(self.peek, group=self.id)
|
|
54
85
|
self.pop = queueCallsInThreads(self.pop, group=self.id)
|
|
55
86
|
|
|
87
|
+
self.__load__ = queueCallsInThreads(self.__load__, group = "pqueue__load__")
|
|
88
|
+
|
|
89
|
+
if not (resp := self.__load__()):
|
|
90
|
+
raise Exception(f'P-QUEUE DB LOAD ERROR: {resp.log}')
|
|
56
91
|
|
|
57
92
|
@property
|
|
58
93
|
def defaultStorageLocation(self):
|
|
@@ -75,22 +110,97 @@ class PersistentQueue(metaclass=__PersistentQueueSingleton):
|
|
|
75
110
|
'''
|
|
76
111
|
return self.__length
|
|
77
112
|
|
|
113
|
+
def __load__(self) -> Response:
|
|
114
|
+
'''
|
|
115
|
+
load underlying queue database
|
|
116
|
+
'''
|
|
117
|
+
|
|
118
|
+
with Handle(self.dbPath, tables=self.__schema) as handle:
|
|
119
|
+
if not (resp := handle.fetch('data', ['count(*)'],'',[])):
|
|
120
|
+
return resp
|
|
121
|
+
|
|
122
|
+
self.__length = resp.data[0][0]
|
|
123
|
+
|
|
124
|
+
return Ok()
|
|
125
|
+
|
|
126
|
+
def __resolveIndex(self, index:int) -> Response:
|
|
127
|
+
'''
|
|
128
|
+
resolve index and determine if its legitimate
|
|
129
|
+
'''
|
|
130
|
+
if index<0:
|
|
131
|
+
index = self.__length + index
|
|
132
|
+
|
|
133
|
+
if not (0 <= index < self.__length):
|
|
134
|
+
return Error(f'invalid index given for persistent queue of length {self.__length}')
|
|
135
|
+
|
|
136
|
+
return Ok(index)
|
|
137
|
+
|
|
138
|
+
@enforceRequirements
|
|
78
139
|
def append(self, data:__allowedDataTypes, /) -> Response:
|
|
79
140
|
'''
|
|
80
141
|
append to the queue
|
|
81
142
|
Args:
|
|
82
143
|
data(__allowedDataTypes): the data to insert, use `obj.allowedDataTypes` to get the list of allowed data types
|
|
83
144
|
'''
|
|
84
|
-
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
dataType = type(data).__name__
|
|
148
|
+
except:
|
|
149
|
+
return Error(f'failed to get data-type of `{data}`')
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
with Handle(self.dbPath, readonly=False) as handle:
|
|
153
|
+
if not (resp := handle.insert('data', [
|
|
154
|
+
dates.currentTimestamp(),
|
|
155
|
+
dataType,
|
|
156
|
+
data
|
|
157
|
+
])):
|
|
158
|
+
return resp
|
|
159
|
+
|
|
160
|
+
self.__length += 1
|
|
161
|
+
return Ok(self.__length)
|
|
85
162
|
|
|
86
163
|
def peek(self, index:int, /) -> Response:
|
|
87
164
|
'''
|
|
88
165
|
get data at `index` without popping it from the queue
|
|
89
166
|
'''
|
|
90
|
-
|
|
167
|
+
if not (resp := self.__resolveIndex(index)): return resp
|
|
168
|
+
index = resp.data
|
|
169
|
+
|
|
170
|
+
with Handle(self.dbPath) as handle:
|
|
171
|
+
if not (resp := handle.fetch('data', ['dataType', 'data'],'',[],limit=1, offset=index, parseJson=True)):
|
|
172
|
+
return resp
|
|
173
|
+
|
|
174
|
+
dataType, data = resp.data[0]
|
|
91
175
|
|
|
92
|
-
|
|
176
|
+
try:
|
|
177
|
+
data = self.__dataTypeCasts[dataType](data)
|
|
178
|
+
except:
|
|
179
|
+
return Error(f'failed to convert value to data-type `{dataType}`. the database was most likely corrupted')
|
|
180
|
+
|
|
181
|
+
return Ok(data)
|
|
182
|
+
|
|
183
|
+
def pop(self, index:int = 0, /) -> Response:
|
|
93
184
|
'''
|
|
94
185
|
get data at `index` and remove it from the queue
|
|
95
186
|
'''
|
|
96
|
-
|
|
187
|
+
if not (resp := self.__resolveIndex(index)): return resp
|
|
188
|
+
index = resp.data
|
|
189
|
+
|
|
190
|
+
with Handle(self.dbPath, readonly=False) as handle:
|
|
191
|
+
if not (resp := handle.fetch('data', ['rowid','dataType', 'data'],'',[],limit=1, offset=index, parseJson=True)):
|
|
192
|
+
return resp
|
|
193
|
+
|
|
194
|
+
rowId, dataType, data = resp.data[0]
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
data = self.__dataTypeCasts[dataType](data)
|
|
198
|
+
except:
|
|
199
|
+
return Error(f'failed to convert value to data-type `{dataType}`. the database was most likely corrupted')
|
|
200
|
+
|
|
201
|
+
if not (resp := handle.delete('data','rowid=?',[rowId])):
|
|
202
|
+
return resp
|
|
203
|
+
|
|
204
|
+
self.__length -= 1
|
|
205
|
+
|
|
206
|
+
return Ok(data)
|
|
@@ -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=hWxkW21lgViOqFijxL4cD-Wpt4koWz6jzcE1v0IiT1c,54341
|
|
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
|
|
@@ -18,7 +18,7 @@ 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
20
|
kisa_utils/queues/__init__.py,sha256=VvhceyN5qeiMel1JFQwLRuVk48oBXaWvDtriCubDOms,48
|
|
21
|
-
kisa_utils/queues/persistent.py,sha256=
|
|
21
|
+
kisa_utils/queues/persistent.py,sha256=e4s2SK0Wvqq8wVcqPm1Gr_4ip0-0l25WKcMUNTSK_QY,6665
|
|
22
22
|
kisa_utils/queues/callables/__init__.py,sha256=OJL3AQnaAS1Eek4H6WBH3WefA2wf-x03cwFmRSK8hoU,141
|
|
23
23
|
kisa_utils/queues/callables/enqueueFunctionCalls.py,sha256=VIliaMvw4MUdOqts0dXdZCYNxs-QrOVjIRAR3scGrRM,11786
|
|
24
24
|
kisa_utils/queues/callables/executorQueues.py,sha256=x6bAqxBSZRZ_kL8CK1lSN6JYAYFLxzM84LC1RmwaOLw,6626
|
|
@@ -27,7 +27,7 @@ kisa_utils/servers/flask.py,sha256=XZYY1pWnP1mSvaS5Uv8G3EFJV5BJBQtU2gDbO8suvLc,4
|
|
|
27
27
|
kisa_utils/structures/__init__.py,sha256=JBU1j3A42jQ62ALKnsS1Hav9YXcYwjDw1wQJtohXPbU,83
|
|
28
28
|
kisa_utils/structures/utils.py,sha256=665rXIapGwFqejizeJwy3DryeskCQOdgP25BCdLkGvk,2898
|
|
29
29
|
kisa_utils/structures/validator.py,sha256=JhD9jcfbjTwBr_7OfuNaJd_cYr7wR2emFhsCEo5MCHQ,4323
|
|
30
|
-
kisa_utils-0.42.
|
|
31
|
-
kisa_utils-0.42.
|
|
32
|
-
kisa_utils-0.42.
|
|
33
|
-
kisa_utils-0.42.
|
|
30
|
+
kisa_utils-0.42.2.dist-info/METADATA,sha256=LQ23FZcJPYknC1FVxA5zJGCnNK8gqHlB13iJymAbU4k,477
|
|
31
|
+
kisa_utils-0.42.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
32
|
+
kisa_utils-0.42.2.dist-info/top_level.txt,sha256=GFOLXZYqpBG9xtscGa2uGJAEiZ5NwsqHBH9NylnB29M,11
|
|
33
|
+
kisa_utils-0.42.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|