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 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=True,
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:
@@ -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
- if id not in cls._instances:
19
- cls._instances[id] = super().__call__(*args, **kwargs)
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[id]
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
- def pop(self, /, *, index:int = 0) -> Response:
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kisa-utils
3
- Version: 0.42.0
3
+ Version: 0.42.2
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=cYlUXDkVCTotsa4pFmoYFkrwPHDEYYeSWJVgIHYRhFQ,53792
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=FTIdz1iPrBnICZ3XYuRDtfXonZP83GuCpsNkPUQi2Lg,3269
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.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,,
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,,