kisa-utils 0.37.3__py3-none-any.whl → 0.37.4__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/cache.py CHANGED
@@ -8,6 +8,7 @@ import time
8
8
  from . import threads
9
9
  from . import queues
10
10
  from . import db
11
+ from typing import Callable
11
12
 
12
13
  class CacheException(Exception):
13
14
  pass
@@ -17,7 +18,12 @@ class _EmptyCacheValue:
17
18
 
18
19
  class Cache:
19
20
  __ACTIVE_CACHES = {}
20
- def __init__(self, name:str):
21
+ def __init__(self, name:str) -> None:
22
+ '''
23
+ create a cache instance
24
+ Args:
25
+ name(str): the cache name
26
+ '''
21
27
  self.__cache = {
22
28
  # key: {
23
29
  # 'lastUpdated': int,
@@ -75,23 +81,30 @@ class Cache:
75
81
  self.__cache[key]['value'] = value
76
82
  self.__cache[key]['lastUpdated'] = now
77
83
 
78
- def addKey(self, name:str, updator:callable, autoUpdateAfter:int=3600, duplicateUpdateThreshold:float=30) -> dict:
84
+ def addKey(self, name:str, updator:Callable, autoUpdateAfter:int=3600, duplicateUpdateThreshold:float=30) -> dict:
79
85
  '''
80
86
  create a new cache key
81
87
 
82
- @param `name`: the name of the key
83
- @param `updator`: the function to call everytime we need to update the key.
84
- The value of the key is whatever the function returns.
85
- Also, the `updator` should not take any arguments or keywords
86
- @param `autoUpdateAfter`: how many SECONDS to wait before we automatically call the `updator` function
87
- @param `duplicateUpdateThreshold`: SECONDS within which if multiple requests to update the key will be ignored as long as one is already activated.
88
- say we have 20 calls to update a key in 1 minute, only 1 request will be obeyed to avoid strange deadlocks and database contension-resources
88
+ Args:
89
+ name(str): the name of the key
90
+ updator(Callable): the function to call everytime we need to update the key.
91
+ The value of the key is whatever the function returns.
92
+ Also, the `updator` should not take any arguments or keywords
93
+ autoUpdateAfter(int): how many SECONDS to wait before we automatically call the `updator` function
94
+ duplicateUpdateThreshold(float): SECONDS within which if multiple requests to update the key will be ignored as long as one is already activated.
95
+ say we have 20 calls to update a key in 1 minute, only 1 request will be obeyed to avoid strange deadlocks and database contension-resources
89
96
 
90
- @returns {'status':BOOL, 'log':STR}
91
-
97
+ Returns:
98
+ ```
99
+ {
100
+ 'status':bool,
101
+ 'log':str
102
+ }
103
+ ```
104
+
92
105
  NB: the result of the `updator` will be ignored if;
93
106
  - its `None`
94
- - its a `dict` with `status=False`
107
+ - its a `dict` with `status` set to `False`
95
108
  - the `updator` raised an exception
96
109
  '''
97
110
  reply = {'status':False, 'log':''}
@@ -131,10 +144,16 @@ class Cache:
131
144
  def triggerKeyUpdate(self, key:str) -> dict:
132
145
  '''
133
146
  trigger update value of a key
134
-
135
- @param `key`: name of the cache key
136
-
137
- @returns {'status':BOOL, 'log':STR}
147
+ Args:
148
+ key(str): name of the cache key
149
+
150
+ Returns:
151
+ ```
152
+ {
153
+ 'status':bool,
154
+ 'log':str
155
+ }
156
+ ```
138
157
  '''
139
158
  reply = {'status':False, 'log':''}
140
159
 
@@ -147,10 +166,17 @@ class Cache:
147
166
  def getValue(self, key:str) -> dict:
148
167
  '''
149
168
  attempt to get the value of a key set in the cache
150
-
151
- @param `key`: name of the cache key
152
-
153
- @returns {'status':BOOL, 'log':STR, 'value':Any}
169
+ Args:
170
+ key(str): name of the cache key
171
+
172
+ Returns:
173
+ ```
174
+ {
175
+ 'status':bool,
176
+ 'log':str,
177
+ 'value':Any
178
+ }
179
+ ```
154
180
  '''
155
181
 
156
182
  reply = {'status':False, 'log':'', 'value':None}
@@ -170,10 +196,17 @@ class Cache:
170
196
  def create(name:str) -> dict:
171
197
  '''
172
198
  create a new cache object
173
-
174
- @param `name`: the name of the cache
175
-
176
- @returns {'status':BOOL, 'log':STR, 'cache':CACHE_OBJECT}
199
+ Args:
200
+ name(str): the name of the cache
201
+
202
+ Returns:
203
+ ```
204
+ {
205
+ 'status':bool,
206
+ 'log':str,
207
+ 'cache': Cache
208
+ }
209
+ ```
177
210
  '''
178
211
  reply = {'status':False, 'log':'', 'cache':None}
179
212
 
@@ -190,6 +223,11 @@ def create(name:str) -> dict:
190
223
  return reply
191
224
 
192
225
  def get(name:str) -> Cache | None:
226
+ '''
227
+ get cache by its name
228
+ Args:
229
+ name(str): the cache name
230
+ '''
193
231
  return Cache._Cache__ACTIVE_CACHES.get(name,None)
194
232
 
195
233
  if __name__=='__main__':
kisa_utils/codes.py CHANGED
@@ -7,7 +7,15 @@ __MAX_CODE_LENGTH = 32
7
7
  __ID_SPACE = string.ascii_lowercase + string.ascii_uppercase + string.digits
8
8
 
9
9
  def new(length:int=12, useSeparator:bool=False, xterSet:str=__ID_SPACE) -> str:
10
- 'compexity of the code will be len(__ID_SPACE)^length'
10
+ '''
11
+ generate a new code
12
+ Args:
13
+ length(int): length of the code to generate
14
+ useSeparator(bool): include a separator ('-') in the code
15
+ xterSet(str): the chatacters to use eg '0123456789' ensures the code only has numbers in it
16
+ Note:
17
+ compexity of the code will be len(xterSet)^length
18
+ '''
11
19
 
12
20
  assert(length>0 and length<=__MAX_CODE_LENGTH)
13
21
  code = ''
kisa_utils/config.py CHANGED
@@ -32,7 +32,9 @@ def _getTopicAndKey(path:str) -> list[str,str]:
32
32
 
33
33
  def getValue(path:str) -> Any|None:
34
34
  '''
35
- path: topicName/keyName eg 'topic1/key1'
35
+ get the value of a set topic-key path
36
+ Args:
37
+ path(str): topicName/keyName eg 'topic1/key1'
36
38
  '''
37
39
 
38
40
  topic, key = _getTopicAndKey(path)
@@ -41,7 +43,9 @@ def getValue(path:str) -> Any|None:
41
43
 
42
44
  def setValue(path:str, value:Any) -> bool:
43
45
  '''
44
- path: topicName/keyName eg 'topic1/key1'
46
+ set the value of a set topic-key path
47
+ Args:
48
+ path(str): topicName/keyName eg 'topic1/key1'
45
49
  '''
46
50
  topic, key = _getTopicAndKey(path)
47
51
 
@@ -58,6 +62,11 @@ def setValue(path:str, value:Any) -> bool:
58
62
  return True
59
63
 
60
64
  def getTopic(topic:str) -> dict|None:
65
+ '''
66
+ get topic data
67
+ Args:
68
+ topic(str): the topic name
69
+ '''
61
70
  topic,_ = _getTopicAndKey(f'{topic}/_')
62
71
  data = None
63
72
 
@@ -72,9 +81,10 @@ def getTopic(topic:str) -> dict|None:
72
81
  return data
73
82
 
74
83
  def getConfigPath() -> str:
84
+ '''get path to the config files root directory'''
75
85
  return __config_root_path
76
86
 
77
- def init():
87
+ def __init__() -> None:
78
88
  global __config_root_path
79
89
  _rootPath = getValue('_sys_/configPath')
80
90
  if _rootPath:
@@ -82,4 +92,4 @@ def init():
82
92
  else:
83
93
  setValue('_sys_/configPath', __config_root_path)
84
94
 
85
- init()
95
+ __init__()
kisa_utils/dates.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import datetime
2
2
  import time
3
- from typing import Tuple
4
3
 
5
4
  WEEK_DAYS = {
6
5
  1:'Monday', 2:'Tuesday', 3:'Wednesday',
@@ -8,8 +7,16 @@ WEEK_DAYS = {
8
7
  7: 'Sunday'
9
8
  }
10
9
 
11
- def weekRange(date:str|datetime.datetime) -> Tuple[str,str]: # assumes standardizedDates to be given
12
- 'return week range (monday-sunday) in thich a date(YYYY-MM-DD) belongs'
10
+ def weekRange(date:str|datetime.datetime) -> tuple[str,str]: # assumes standardizedDates to be given
11
+ '''
12
+ return a list containing the monday and sunday that belong to the week
13
+ Args:
14
+ date(str|datetime.datetime): the date whose week-range we need to get
15
+ Returns:
16
+ ```
17
+ (monday:str, sunday:str) # each date is in format "YYYY-MM-DD"
18
+ ```
19
+ '''
13
20
 
14
21
  if isinstance(date,(str,bytes)):
15
22
  date = datetime.datetime.strptime(date, '%Y-%m-%d')
@@ -22,8 +29,17 @@ def weekRange(date:str|datetime.datetime) -> Tuple[str,str]: # assumes standardi
22
29
  # assumes standardizedDates to be given
23
30
  def humanizeDate(date:str, includeWeekDay:bool=False) -> str:
24
31
  '''
25
- convert 'YYYY-MM-DD' to 'DD MonthCode YYYY' eg
26
- 2021-09-23 - > '23 Sep 2021' || ''Thu 23 Sep 2021''
32
+ convert date into a human-friendly format
33
+ Args:
34
+ date(str): the date to humanize. its in the `YYYY-MM-DD` format
35
+ includeWeekDay(bool): wheather or not to include the week-day
36
+ Returns:
37
+ `str` in format `DD MonthCode YYYY`
38
+ Examples:
39
+ ```
40
+ humanizeDate('2021-09-23') -> '23 Sep 2021'
41
+ humanizeDate('2021-09-23', True) -> 'Thu 23 Sep 2021'
42
+ ```
27
43
  '''
28
44
 
29
45
  if includeWeekDay:
@@ -35,20 +51,67 @@ def humanizeDate(date:str, includeWeekDay:bool=False) -> str:
35
51
  #return f'{date[2]} {MONTH_CODES[date[1]]} {date[0]}'
36
52
 
37
53
  def dehumanizeDate(date:str) -> str:
54
+ '''
55
+ convert date from human-friendly format to YYYY-MM-DD
56
+ Args:
57
+ date(str): the humanized date to dehumanize
58
+ Examples:
59
+ ```
60
+ dehumanizeDate('23 Sep 2021') -> '2021-09-23'
61
+ dehumanizeDate('Thu 23 Sep 2021') -> '2021-09-23'
62
+ ```
63
+
64
+ '''
38
65
  if 2==date.count(' '):
39
66
  return datetime.datetime.strptime(date, '%d %b %Y').strftime('%Y-%m-%d')
40
67
 
41
68
  return datetime.datetime.strptime(date, '%a %d %b %Y').strftime('%Y-%m-%d')
42
69
 
43
- def daysBetweenDates(dateFrom, dateTo): # assumes standardizedDates to be given
44
- 'dates given are in format YYYY-MM-DD'
70
+ def daysBetweenDates(dateFrom:str, dateTo:str) -> int: # assumes standardizedDates to be given
71
+ '''
72
+ get the number of days between dates, not inclusive
73
+ Args:
74
+ dateFrom(str): the start date in `YYYY-MM-DD` format
75
+ dateTo(str): end date in `YYYY-MM-DD` format
76
+ Note:
77
+ the days are not inclusive in the count
78
+ Examples:
79
+ ```
80
+ daysBetweenDates(monday, tuesday) -> 1 # both days in the same week
81
+ ```
82
+ '''
83
+
45
84
  return (datetime.datetime.strptime(dateTo, '%Y-%m-%d') - datetime.datetime.strptime(dateFrom, '%Y-%m-%d')).days
46
85
 
47
- def daysBetweenDatesInlusive(dateFrom:str, dateTo:str): # assumes standardizedDates to be given
86
+ def daysBetweenDatesInlusive(dateFrom:str, dateTo:str) -> str: # assumes standardizedDates to be given
87
+ '''
88
+ get the number of days between dates, inclusive
89
+ Args:
90
+ dateFrom(str): the start date in `YYYY-MM-DD` format
91
+ dateTo(str): end date in `YYYY-MM-DD` format
92
+ Note:
93
+ the days are not inclusive in the count
94
+ Examples:
95
+ ```
96
+ daysBetweenDatesInlusive(monday, tuesday) -> 2 # both days in the same week, monday is included in the count
97
+ ```
98
+ '''
48
99
  days = daysBetweenDates(dateFrom, dateTo)
49
100
  return (abs(days)+1) * (-1 if days<0 else +1)
50
101
 
51
- def howLongAgo(dateFrom, dateTo, shortestVersion:bool=True):
102
+ def howLongAgo(dateFrom:str, dateTo:str, shortestVersion:bool=True) -> str:
103
+ '''
104
+ get a human-friendly how-long-ago eg '3 days', '1 week, 4 days', '3 months, 1 week, 6 days', etc
105
+ Args:
106
+ dateFrom(str): the start date in the YYYY-MM-DD format
107
+ dateTo(str): the end date in the YYYY-MM-DD format
108
+ shortestVersion(bool): return the shorted possible version
109
+ Examples:
110
+ ```
111
+ howLongAgo('2025-01-01', '2025-05-18') -> '4 months, 2 weeks'
112
+ howLongAgo('2025-01-01', '2025-05-18', False) -> '4 months, 2 weeks, 3 days'
113
+ ```
114
+ '''
52
115
  days = daysBetweenDates(dateFrom, dateTo)
53
116
 
54
117
  return_str = ''
@@ -82,28 +145,78 @@ def howLongAgo(dateFrom, dateTo, shortestVersion:bool=True):
82
145
 
83
146
  return return_str
84
147
 
85
- def currentTimestamp():
148
+ def currentTimestamp() -> str:
86
149
  '''
87
- get current timestamp in EAT, YYYY-MM-DD HH:MM:SS
150
+ get current timestamp in EAT
151
+ Returns:
152
+ string in format `YYYY-MM-DD HH:MM:SS`
88
153
  '''
89
154
  now = datetime.datetime.utcnow() + datetime.timedelta(hours=3.00)
90
155
  return str(now)[:19]
91
156
 
92
- def dateFrom(date,days,hours=0):
93
- 'date: YYYY-MM-DD'
94
- date = datetime.datetime.strptime(date, '%Y-%m-%d')
95
- return str(date+datetime.timedelta(days=days, hours=hours))[:10]
157
+ def dateFrom(date:str, days:int, hours:int=0) -> str:
158
+ '''
159
+ get the date thats `days` + `hours` from `date`
160
+ Args:
161
+ date(str): the reference date to use format is `YYYY-MM-DD`
162
+ days(int): days from the reference date
163
+ hours(int): hours to include to the days to ge tthe new date
164
+ Examples:
165
+ ```
166
+ dateFrom('2025-06-18', 1) -> '2025-06-19'
167
+ dateFrom('2025-01-01', -2) -> '2024-12-30'
168
+ ```
169
+ '''
170
+ _date = datetime.datetime.strptime(date, '%Y-%m-%d')
171
+ return str(_date + datetime.timedelta(days=days, hours=hours))[:10]
96
172
 
97
- def dateFromNow(days=0,hours=0):
173
+ def dateFromNow(days=0,hours=0) -> str:
98
174
  '''
99
- get current timestamp in EAT, YYYY-MM-DD HH:MM:SS
175
+ get the timestamp(not date) thats `days` + `hours` from the current time
176
+ Args:
177
+ days(int): days from the reference date
178
+ hours(int): hours to include to the days to ge tthe new date
179
+ Examples:
180
+ ```
181
+ # assuming the current time is '2025-06-18 10:53:54'
182
+ dateFromNow(1) -> '2025-06-19 10:53:54'
183
+ dateFromNow(-2) -> '2025-06-16 10:53:54'
184
+ ```
100
185
  '''
101
186
  now = datetime.datetime.utcnow() + datetime.timedelta(hours=3.00)
102
187
  return str(now+datetime.timedelta(days=days, hours=hours))[:19]
103
188
 
104
189
  # thanks to airtel & MTN strange date formats, we have this
105
- def standardizedDate(date:str, fmt=''):
106
- 'return "YYYY-MM-DD HH:MM:SS"'
190
+ def standardizedDate(date:str, fmt:str=''):
191
+ '''
192
+ standardize date/timestamp into the `YYYY-MM-DD HH:MM:SS` standard
193
+ Args:
194
+ date(str): the date to standardize
195
+ fmt(str): the format in which the date/timestamp is currently in
196
+ Note:
197
+ Supported formats;
198
+ - DD-MM-YYYY HH:MM:SS
199
+ - MM-DD-YYYY HH:MM:SS
200
+
201
+ - DD-MM-YYYY HH:MM:SS AM
202
+ - MM-DD-YYYY HH:MM:SS AM
203
+
204
+ - DD-MM-YYYY HH:MM
205
+ - MM-DD-YYYY HH:MM
206
+
207
+ - DD-MM-YYYY HH:MM AM
208
+ - MM-DD-YYYY HH:MM AM
209
+
210
+ - YYYY-MM-DD
211
+ - DD-MM-YYYY
212
+ - MM-DD-YYYY
213
+
214
+ - YYYY-MM-DD HH:MM:SS
215
+ - YYYY-MM-DD HH:MM:SS AM
216
+
217
+ - YYYY-MM-DD HH:MM
218
+ - YYYY-MM-DD HH:MM AM
219
+ '''
107
220
  _date = None
108
221
 
109
222
  date = date.replace('/','-').replace('"','').replace("'",'')
@@ -112,7 +225,7 @@ def standardizedDate(date:str, fmt=''):
112
225
  if len(date)<len('YYYY-MM-DD'): raise ValueError('invalid date given')
113
226
  if not fmt: raise ValueError('no format given')
114
227
 
115
- fmt = fmt.upper().replace('PM','AM')
228
+ fmt:str|None = fmt.upper().replace('PM','AM')
116
229
 
117
230
  fmt = {
118
231
  'DD-MM-YYYY HH:MM:SS':'%d-%m-%Y %H:%M:%S',
@@ -167,10 +280,18 @@ def _testStandardizeTime():
167
280
  ]:
168
281
  print(date,'->',standardizedDate(date,fmt))
169
282
 
170
- def lastWeekOn(day:int, humanize:bool=False):
283
+ def lastWeekOn(day:int, humanize:bool=False) -> str:
171
284
  '''
172
- day: 1=Monday, 7=Sunday
173
- get date of day in last week eg lastWeekOn(1) -> date of monday, last week
285
+ get the date(YYYY-MM-DD) on `day`, last week
286
+ Args:
287
+ day(int): the week day; Mon=1, Tue=2, ..., Sun=7
288
+ humanize(bool): humanize the date
289
+ Examples:
290
+ ```
291
+ # assuming the current date is '2025-06-18'
292
+ lastWeekOn(3) -> '2025-06-11'
293
+ lastWeekOn(3, True) -> '11 Jun 2025'
294
+ ```
174
295
  '''
175
296
  assert isinstance(day,int) and 1<=day<=7
176
297
 
@@ -185,25 +306,36 @@ def lastWeekOn(day:int, humanize:bool=False):
185
306
  return strDate
186
307
 
187
308
  def secondsSinceEpoch()->int:
309
+ '''get the seconds since epoch'''
188
310
  return int(time.time())
189
311
 
190
312
  def minutesSinceEpoch()->int:
313
+ '''get minutes since epoch'''
191
314
  return secondsSinceEpoch() // 60
192
315
 
193
316
  def today() -> str:
317
+ '''get current date in YYYY-MM-DD format'''
194
318
  return currentTimestamp()[:10]
195
319
 
196
320
  def tomorrow() -> str:
321
+ '''get tomorrow's date in YYYY-MM-DD format'''
197
322
  return dateFromNow(days=+1)[:10]
198
323
 
199
324
  def yesterday() -> str:
325
+ '''get yesterday's date in YYYY-MM-DD format'''
200
326
  return dateFromNow(days=-1)[:10]
201
327
 
202
328
  def endOfMonth(year:str|int, month:str|int) -> str:
203
329
  '''
204
- get the last date of the given month
205
- @arg `year`: str|int, 4 characters representing the year
206
- @arg `month`: str|int, 1|2 characters representing the month
330
+ get the last date(YYYY-MM-DD) of the given month
331
+ Args:
332
+ year(str|int): 4 characters representing the year
333
+ month(str|int): 1|2 characters representing the month
334
+ Examples:
335
+ ```
336
+ endOfMonth(2025, 6) -> '2025-06-30'
337
+ endOfMonth(2025, 2) -> '2025-02-28'
338
+ ```
207
339
  '''
208
340
 
209
341
  year = f'{year}'
@@ -228,7 +360,7 @@ def endOfMonth(year:str|int, month:str|int) -> str:
228
360
 
229
361
  def endOfCurrentMonth() -> str:
230
362
  '''
231
- get last date of the current month
363
+ get last date(YYYY-MM-DD) of the current month
232
364
  '''
233
365
 
234
366
  dateToday = today()
@@ -239,8 +371,8 @@ def endOfCurrentMonth() -> str:
239
371
  def dateIsValid(date:str) -> bool:
240
372
  '''
241
373
  check if a date is valid in the format 'YYYY-MM-DD'
242
- @arg `date:str`: the date to check
243
- @return `bool`. True=date is valid, False = date is invalid
374
+ Args:
375
+ date(str): the date to check
244
376
  '''
245
377
  try:
246
378
  standardizedDate(date, 'YYYY-MM-DD')
@@ -250,12 +382,27 @@ def dateIsValid(date:str) -> bool:
250
382
  return True
251
383
 
252
384
  def isWeekend(date:str) -> bool:
385
+ '''
386
+ check if `date` is in a weekend (Saturday or Sunday)
387
+ Args:
388
+ date(str): the date to check
389
+ '''
253
390
  return humanizeDate(date, includeWeekDay=True).split(' ')[0] in ['Sat','Sun']
254
391
 
255
392
  def isSunday(date:str) -> bool:
393
+ '''
394
+ check if `date` is a Sunday
395
+ Args:
396
+ date(str): the date to check
397
+ '''
256
398
  return humanizeDate(date, includeWeekDay=True).split(' ')[0] in ['Sun']
257
399
 
258
400
  def isSaturday(date:str) -> bool:
401
+ '''
402
+ check if `date` is a Saturday
403
+ Args:
404
+ date(str): the date to check
405
+ '''
259
406
  return humanizeDate(date, includeWeekDay=True).split(' ')[0] in ['Sat']
260
407
 
261
408
  if __name__=='__main__':