kisa-utils 0.40.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/__init__.py CHANGED
@@ -11,11 +11,13 @@ from kisa_utils import codes
11
11
  from kisa_utils import log
12
12
  from kisa_utils import token
13
13
  from kisa_utils import threads
14
- from kisa_utils import queues
14
+ from kisa_utils.queues.callables import executorQueues
15
15
  from kisa_utils import cache
16
16
  from kisa_utils import response
17
17
  from kisa_utils import permissions
18
18
  from kisa_utils import functionUtils
19
19
  from kisa_utils import servers
20
- from kisa_utils import enqueue
21
20
  from kisa_utils import dataStructures
21
+
22
+ from kisa_utils import queues
23
+ from kisa_utils.queues.callables import enqueueFunctionCalls
kisa_utils/cache.py CHANGED
@@ -6,7 +6,7 @@ this module is reponsible to creating and managing named caches for KISA
6
6
 
7
7
  import time
8
8
  from . import threads
9
- from . import queues
9
+ from .queues.callables import executorQueues
10
10
  from . import db
11
11
  from typing import Callable
12
12
 
@@ -43,7 +43,7 @@ class Cache:
43
43
  if not loopThreadReply['status']:
44
44
  raise CacheException(f'cache main thread: {loopThreadReply["log"]}')
45
45
 
46
- queueReply = queues.create(f'_cache:{name}', self.__readQueue)
46
+ queueReply = executorQueues.create(f'_cache:{name}', self.__readQueue)
47
47
  if not queueReply['status']:
48
48
  raise CacheException(f'cache queue thread: {queueReply["log"]}')
49
49
 
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,4 @@
1
+ from . import enqueueFunctionCalls
2
+ from . import executorQueues
3
+
4
+ from .enqueueFunctionCalls import queueCallsInThreads, queueCallsInProcesses
@@ -5,14 +5,14 @@ this module is reponsible to creating and managing named queues for KISA
5
5
  '''
6
6
 
7
7
  from queue import Queue as pyQueue
8
- from .structures import validator as structureValidator
9
- from . import threads
8
+ from ...structures import validator as structureValidator
9
+ from ... import threads
10
10
  from typing import Callable, Any
11
11
 
12
- class QueueException(Exception):
12
+ class ExecutorQueueException(Exception):
13
13
  pass
14
14
 
15
- class Queue:
15
+ class ExecutorQueue:
16
16
  __ACTIVE_QUEUES = {}
17
17
 
18
18
  def __init__(self, name:str, executor:Callable, inputDefinitions:dict={}) -> None:
@@ -39,25 +39,25 @@ class Queue:
39
39
  inputDefinitions['kwargs'] = inputDefinitions.get('kwargs',{})
40
40
 
41
41
  if name in activeQueues:
42
- raise QueueException(f'a queue already named `{name}` exists')
42
+ raise ExecutorQueueException(f'a queue already named `{name}` exists')
43
43
 
44
44
  if not callable(executor):
45
- raise QueueException('given `executor` is not a function/callable')
45
+ raise ExecutorQueueException('given `executor` is not a function/callable')
46
46
 
47
47
  executorName = executor.__name__
48
48
  if executorName.startswith('<'):
49
- raise QueueException('lambda functions are not allowed')
49
+ raise ExecutorQueueException('lambda functions are not allowed')
50
50
 
51
51
  if not (
52
52
  isinstance(inputDefinitions, dict) and \
53
53
  ('args' in inputDefinitions) and isinstance(inputDefinitions['args'],(tuple,list)) and\
54
54
  ('kwargs' in inputDefinitions) and isinstance(inputDefinitions['kwargs'],dict) \
55
55
  ):
56
- raise QueueException('invalid `inputDefinitions` given')
56
+ raise ExecutorQueueException('invalid `inputDefinitions` given')
57
57
 
58
58
  for kwarg in inputDefinitions['kwargs']:
59
59
  if not isinstance(kwarg,str):
60
- raise QueueException('all keys in inputDefinitions->kwargs must be strings')
60
+ raise ExecutorQueueException('all keys in inputDefinitions->kwargs must be strings')
61
61
 
62
62
  self.executor = executor
63
63
  self.inputDefinitions = inputDefinitions
@@ -112,7 +112,7 @@ def nameIsRegistered(name:str) -> bool:
112
112
  Args:
113
113
  name(str): the queue name
114
114
  '''
115
- return name in Queue._Queue__ACTIVE_QUEUES
115
+ return name in ExecutorQueue._Queue__ACTIVE_QUEUES
116
116
 
117
117
  def create(name:str, executor:Callable, inputDefinitions:dict={}) -> dict:
118
118
  '''
@@ -148,16 +148,16 @@ def create(name:str, executor:Callable, inputDefinitions:dict={}) -> dict:
148
148
  inputDefinitions['kwargs'] = inputDefinitions.get('kwargs',None) or {}
149
149
 
150
150
  if nameIsRegistered(name):
151
- if inputDefinitions!=Queue._Queue__ACTIVE_QUEUES[name].inputDefinitions:
151
+ if inputDefinitions!=ExecutorQueue._Queue__ACTIVE_QUEUES[name].inputDefinitions:
152
152
  reply['log'] = f'a queue with the name `{name}` already exisits and has different inputDefinitions'
153
153
  else:
154
154
  reply['status'] = True
155
- reply['queue'] = Queue._Queue__ACTIVE_QUEUES[name]
155
+ reply['queue'] = ExecutorQueue._Queue__ACTIVE_QUEUES[name]
156
156
 
157
157
  return reply
158
158
 
159
159
  try:
160
- queue = Queue(name, executor, inputDefinitions)
160
+ queue = ExecutorQueue(name, executor, inputDefinitions)
161
161
  except Exception as e:
162
162
  reply['log'] = f'{e}'
163
163
  return reply
@@ -181,7 +181,7 @@ def push(name:str, *args:tuple, **kwargs:dict) -> dict:
181
181
  '''
182
182
  reply = {'status':False, 'log':''}
183
183
 
184
- queue = Queue._Queue__ACTIVE_QUEUES.get(name,None)
184
+ queue = ExecutorQueue._Queue__ACTIVE_QUEUES.get(name,None)
185
185
 
186
186
  if not queue:
187
187
  reply['log'] = f'could not find queue named `{name}`'
@@ -189,13 +189,13 @@ def push(name:str, *args:tuple, **kwargs:dict) -> dict:
189
189
 
190
190
  return queue.push(*args, **kwargs)
191
191
 
192
- def get(name:str) -> Queue | None:
192
+ def get(name:str) -> ExecutorQueue | None:
193
193
  '''
194
194
  get queue instance using its name
195
195
  Args:
196
196
  name(str): the queue name
197
197
  '''
198
- return Queue._Queue__ACTIVE_QUEUES.get(name,None)
198
+ return ExecutorQueue._Queue__ACTIVE_QUEUES.get(name,None)
199
199
 
200
200
  if __name__=='__main__':
201
201
  def addNumbers(a:int,b:int):
@@ -204,7 +204,7 @@ if __name__=='__main__':
204
204
  queueReply = create('testQueue',addNumbers,{'args':(int,int)})
205
205
  print(queueReply)
206
206
  # queue = queueReply['queue']
207
- queue:Queue = get('testQueue')
207
+ queue:ExecutorQueue = get('testQueue')
208
208
 
209
209
  for i in range(15):
210
210
  print(queue.push(i,10))
@@ -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.40.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
@@ -1,30 +1,33 @@
1
- kisa_utils/__init__.py,sha256=z2vCa2O3W1vOFhW4K0_hkpyEuy_H05WUTzdCPHldCyI,659
2
- kisa_utils/cache.py,sha256=4Ue5G3QhHSQAmIfQKYgWKWjNL4rA4wLLd_RdBLb2ABY,7345
1
+ kisa_utils/__init__.py,sha256=-g5gBmI4gV840-Brg1V1aZ_R_r3n_csB-PlRRdUV_UQ,745
2
+ kisa_utils/cache.py,sha256=WOL3e0wvoyESXTtlbVnUg9TYUoLzg64sLP9J4b4ti9k,7377
3
3
  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
- kisa_utils/enqueue.py,sha256=VIliaMvw4MUdOqts0dXdZCYNxs-QrOVjIRAR3scGrRM,11786
10
9
  kisa_utils/figures.py,sha256=pYIpQzu1OXRSsY1d98GhgPifnIRmgl-r7S32ai-Ms0c,3731
11
10
  kisa_utils/functionUtils.py,sha256=PlXjnmU1uJWNdISlJJ3SCgavTsgNBoebaa9dtWSFhRA,6553
12
11
  kisa_utils/log.py,sha256=0TYdxcIBts026RCSuVIQBcZ-CW1ES7n3M1nEIjmeLTM,2295
13
- kisa_utils/queues.py,sha256=9QqPtDujw6tbWk7uUiXrsd0rVBTIkzeQw9b45l5Fo3k,6502
14
12
  kisa_utils/remote.py,sha256=0RDrfC4RUW4m6JLziC0_EXJYqzWp38Rw8NDroJ0MuqI,2149
15
13
  kisa_utils/response.py,sha256=asETUBkeF5OlSTwa-coa7lZDCKmQlHCmHf6eaZFl8CU,4560
16
14
  kisa_utils/standardize.py,sha256=nt-uzHQFoKxGscD_MpDYXw65Teg3724whAqa6Kh_zhE,2231
17
- kisa_utils/storage.py,sha256=waHLmrf19QxvEQZlWlC2qQvEYFFhDeJHc7MMNbcmj7c,5823
15
+ kisa_utils/storage.py,sha256=6NdEVrHMS7WB_vmCwiGigIinu-EjxalFJhk1kj-_vWs,5990
18
16
  kisa_utils/threads.py,sha256=qQqsf64YHMyLpboq5AEXKxYqf3iXUhxiJe6Ymg-vlxI,12840
19
17
  kisa_utils/token.py,sha256=Y2qglWYWpmHxoXBh-TH0r1as0uPV5LLqMNcunLvM4vM,7850
20
18
  kisa_utils/permissions/__config__.py,sha256=i3ELkOydDnjKx2ozQTxLZdZ8DXSeUncnl2kRxANjFmM,613
21
19
  kisa_utils/permissions/__init__.py,sha256=q7LGl26f-MPXkLS6nxBKDotW3xdB8y7pI5S_Oo5fPOw,47976
20
+ kisa_utils/queues/__init__.py,sha256=VvhceyN5qeiMel1JFQwLRuVk48oBXaWvDtriCubDOms,48
21
+ kisa_utils/queues/persistent.py,sha256=FTIdz1iPrBnICZ3XYuRDtfXonZP83GuCpsNkPUQi2Lg,3269
22
+ kisa_utils/queues/callables/__init__.py,sha256=OJL3AQnaAS1Eek4H6WBH3WefA2wf-x03cwFmRSK8hoU,141
23
+ kisa_utils/queues/callables/enqueueFunctionCalls.py,sha256=VIliaMvw4MUdOqts0dXdZCYNxs-QrOVjIRAR3scGrRM,11786
24
+ kisa_utils/queues/callables/executorQueues.py,sha256=x6bAqxBSZRZ_kL8CK1lSN6JYAYFLxzM84LC1RmwaOLw,6626
22
25
  kisa_utils/servers/__init__.py,sha256=lPqDyGTrFo0qwPZ2WA9Xtcpc5D8AIU4huqgFx1iZf68,19
23
26
  kisa_utils/servers/flask.py,sha256=XZYY1pWnP1mSvaS5Uv8G3EFJV5BJBQtU2gDbO8suvLc,40422
24
27
  kisa_utils/structures/__init__.py,sha256=JBU1j3A42jQ62ALKnsS1Hav9YXcYwjDw1wQJtohXPbU,83
25
28
  kisa_utils/structures/utils.py,sha256=665rXIapGwFqejizeJwy3DryeskCQOdgP25BCdLkGvk,2898
26
29
  kisa_utils/structures/validator.py,sha256=JhD9jcfbjTwBr_7OfuNaJd_cYr7wR2emFhsCEo5MCHQ,4323
27
- kisa_utils-0.40.0.dist-info/METADATA,sha256=OxeJF4Wb_Yk5x5spw-v8bdERToTAICfPzHPs9ntEyhg,477
28
- kisa_utils-0.40.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
29
- kisa_utils-0.40.0.dist-info/top_level.txt,sha256=URxY4sRuqmirOxWtztpVmPoGQdksEMYO6hmYsEDGz2Y,75
30
- kisa_utils-0.40.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,,
@@ -0,0 +1 @@
1
+ kisa_utils
@@ -1,4 +0,0 @@
1
- kisa_utils
2
- kisa_utils/permissions
3
- kisa_utils/servers
4
- kisa_utils/structures