encommon 0.10.0__py3-none-any.whl → 0.11.1__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.
Files changed (45) hide show
  1. encommon/config/__init__.py +3 -3
  2. encommon/config/config.py +19 -7
  3. encommon/config/files.py +3 -3
  4. encommon/config/logger.py +39 -7
  5. encommon/config/params.py +21 -1
  6. encommon/config/paths.py +2 -2
  7. encommon/config/test/test_logger.py +4 -1
  8. encommon/config/test/{test_common.py → test_utils.py} +3 -3
  9. encommon/config/{common.py → utils.py} +1 -10
  10. encommon/conftest.py +2 -1
  11. encommon/crypts/crypts.py +68 -18
  12. encommon/crypts/params.py +14 -1
  13. encommon/crypts/test/test_crypts.py +56 -13
  14. encommon/times/__init__.py +14 -2
  15. encommon/times/common.py +0 -127
  16. encommon/times/params.py +155 -0
  17. encommon/times/parse.py +5 -5
  18. encommon/times/test/test_params.py +64 -0
  19. encommon/times/test/test_parse.py +1 -1
  20. encommon/times/test/test_timer.py +86 -0
  21. encommon/times/test/test_timers.py +81 -41
  22. encommon/times/test/{test_common.py → test_utils.py} +3 -3
  23. encommon/times/test/test_window.py +101 -51
  24. encommon/times/test/test_windows.py +250 -0
  25. encommon/times/timer.py +147 -0
  26. encommon/times/timers.py +217 -130
  27. encommon/times/times.py +6 -6
  28. encommon/times/utils.py +148 -0
  29. encommon/times/window.py +124 -85
  30. encommon/times/windows.py +472 -0
  31. encommon/types/notate.py +0 -2
  32. encommon/utils/__init__.py +2 -2
  33. encommon/utils/common.py +0 -39
  34. encommon/utils/files.py +71 -0
  35. encommon/utils/paths.py +1 -1
  36. encommon/utils/sample.py +2 -2
  37. encommon/utils/test/{test_common.py → test_files.py} +2 -2
  38. encommon/utils/test/test_paths.py +2 -1
  39. encommon/version.txt +1 -1
  40. {encommon-0.10.0.dist-info → encommon-0.11.1.dist-info}/METADATA +1 -1
  41. encommon-0.11.1.dist-info/RECORD +73 -0
  42. encommon-0.10.0.dist-info/RECORD +0 -65
  43. {encommon-0.10.0.dist-info → encommon-0.11.1.dist-info}/LICENSE +0 -0
  44. {encommon-0.10.0.dist-info → encommon-0.11.1.dist-info}/WHEEL +0 -0
  45. {encommon-0.10.0.dist-info → encommon-0.11.1.dist-info}/top_level.txt +0 -0
encommon/times/timers.py CHANGED
@@ -7,82 +7,98 @@ is permitted, for more information consult the project license file.
7
7
 
8
8
 
9
9
 
10
+ from copy import deepcopy
10
11
  from sqlite3 import Connection
11
12
  from sqlite3 import connect as SQLite
12
13
  from typing import Optional
14
+ from typing import TYPE_CHECKING
13
15
 
14
- from .common import NUMERIC
15
16
  from .common import PARSABLE
17
+ from .timer import Timer
16
18
  from .times import Times
17
19
 
20
+ if TYPE_CHECKING:
21
+ from .params import TimerParams
22
+ from .params import TimersParams
23
+
18
24
 
19
25
 
20
26
  CACHE_TABLE = (
21
27
  """
22
28
  create table if not exists
23
29
  {0} (
30
+ "group" text not null,
24
31
  "unique" text not null,
25
32
  "update" text not null,
26
- primary key ("unique"));
33
+ primary key (
34
+ "group", "unique"));
27
35
  """) # noqa: LIT003
28
36
 
29
37
 
30
38
 
31
- _TIMERS = dict[str, float]
32
- _CACHED = dict[str, Times]
39
+ TIMERS = dict[str, Timer]
33
40
 
34
41
 
35
42
 
36
43
  class Timers:
37
44
  """
38
- Track timers on unique key and determine when to proceed.
45
+ Track timers on unique key determining when to proceed.
39
46
 
40
47
  .. warning::
41
48
  This class will use an in-memory database for cache,
42
49
  unless a cache file is explicity defined.
43
50
 
44
51
  .. testsetup::
52
+ >>> from .params import TimerParams
53
+ >>> from .params import TimersParams
45
54
  >>> from time import sleep
46
55
 
47
56
  Example
48
57
  -------
49
- >>> timers = Timers({'one': 1})
58
+ >>> source = {'one': TimerParams(timer=1)}
59
+ >>> params = TimersParams(timers=source)
60
+ >>> timers = Timers(params)
50
61
  >>> timers.ready('one')
51
62
  False
52
63
  >>> sleep(1)
53
64
  >>> timers.ready('one')
54
65
  True
55
66
 
56
- :param timers: Seconds that are used for each of timers.
57
- :param file: Optional path to SQLite database for
58
- cache. This will allow for use between executions.
59
- :param table: Optional override default table name.
67
+ :param params: Parameters for instantiating the instance.
68
+ :param file: Optional path to file for SQLite database,
69
+ allowing for state retention between the executions.
70
+ :param table: Optional override for default table name.
71
+ :param group: Optional override for default group name.
60
72
  """
61
73
 
62
- __config: _TIMERS
74
+ __params: 'TimersParams'
75
+
63
76
  __sqlite: Connection
64
77
  __file: str
65
78
  __table: str
66
- __cache: _CACHED
79
+ __group: str
80
+
81
+ __timers: TIMERS
67
82
 
68
83
 
69
84
  def __init__(
70
85
  self,
71
- timers: Optional[dict[str, NUMERIC]] = None,
86
+ params: Optional['TimersParams'] = None,
87
+ *,
72
88
  file: str = ':memory:',
73
89
  table: str = 'timers',
90
+ group: str = 'default',
74
91
  ) -> None:
75
92
  """
76
93
  Initialize instance for class using provided parameters.
77
94
  """
78
95
 
96
+ from .params import TimersParams
79
97
 
80
- timers = dict(timers or {})
98
+ if params is None:
99
+ params = TimersParams()
81
100
 
82
- items = timers.items()
83
-
84
- for key, value in items:
85
- timers[key] = float(value)
101
+ self.__params = deepcopy(params)
86
102
 
87
103
 
88
104
  sqlite = SQLite(file)
@@ -93,113 +109,71 @@ class Timers:
93
109
 
94
110
  sqlite.commit()
95
111
 
96
-
97
- cached: _CACHED = {}
98
-
99
- for timer in timers:
100
- cached[timer] = Times()
101
-
102
-
103
- self.__config = timers
104
112
  self.__sqlite = sqlite
105
113
  self.__file = file
106
114
  self.__table = table
107
- self.__cache = cached
115
+ self.__group = group
116
+
108
117
 
118
+ self.__timers = {}
109
119
 
110
- self.load_cache()
111
- self.save_cache()
120
+ self.load_children()
112
121
 
113
122
 
114
- def load_cache(
123
+ @property
124
+ def params(
115
125
  self,
116
- ) -> None:
126
+ ) -> 'TimersParams':
117
127
  """
118
- Load the timers cache from the database into attribute.
119
- """
120
-
121
- cached = self.__sqlite
122
- table = self.__table
123
- cachem = self.__cache
124
-
125
- cursor = cached.execute(
126
- f'select * from {table}'
127
- ' order by "unique" asc')
128
+ Return the Pydantic model containing the configuration.
128
129
 
129
- records = cursor.fetchall()
130
-
131
- for record in records:
132
-
133
- unique = record[0]
134
- update = record[1]
135
-
136
- times = Times(update)
130
+ :returns: Pydantic model containing the configuration.
131
+ """
137
132
 
138
- cachem[unique] = times
133
+ return self.__params
139
134
 
140
135
 
141
- def save_cache(
136
+ @property
137
+ def sqlite(
142
138
  self,
143
- ) -> None:
144
- """
145
- Save the timers cache from the attribute into database.
139
+ ) -> Connection:
146
140
  """
141
+ Return the value for the attribute from class instance.
147
142
 
148
- insert = tuple[str, str]
149
- inserts: list[insert] = []
150
-
151
-
152
- cached = self.__sqlite
153
- table = self.__table
154
- cachem = self.__cache
155
-
156
-
157
- items = cachem.items()
158
-
159
- for key, value in items:
160
-
161
- append = (key, str(value))
162
-
163
- inserts.append(append)
164
-
165
-
166
- cached.executemany(
167
- (f'replace into {table}'
168
- ' ("unique", "update")'
169
- ' values (?, ?)'),
170
- tuple(sorted(inserts)))
143
+ :returns: Value for the attribute from class instance.
144
+ """
171
145
 
172
- cached.commit()
146
+ return self.__sqlite
173
147
 
174
148
 
175
149
  @property
176
- def timers(
150
+ def file(
177
151
  self,
178
- ) -> _TIMERS:
152
+ ) -> str:
179
153
  """
180
154
  Return the value for the attribute from class instance.
181
155
 
182
156
  :returns: Value for the attribute from class instance.
183
157
  """
184
158
 
185
- return dict(self.__config)
159
+ return self.__file
186
160
 
187
161
 
188
162
  @property
189
- def sqlite(
163
+ def table(
190
164
  self,
191
- ) -> Connection:
165
+ ) -> str:
192
166
  """
193
167
  Return the value for the attribute from class instance.
194
168
 
195
169
  :returns: Value for the attribute from class instance.
196
170
  """
197
171
 
198
- return self.__sqlite
172
+ return self.__table
199
173
 
200
174
 
201
175
  @property
202
- def file(
176
+ def group(
203
177
  self,
204
178
  ) -> str:
205
179
  """
@@ -208,33 +182,130 @@ class Timers:
208
182
  :returns: Value for the attribute from class instance.
209
183
  """
210
184
 
211
- return self.__file
185
+ return self.__group
212
186
 
213
187
 
214
188
  @property
215
- def table(
189
+ def children(
216
190
  self,
217
- ) -> str:
191
+ ) -> dict[str, Timer]:
218
192
  """
219
193
  Return the value for the attribute from class instance.
220
194
 
221
195
  :returns: Value for the attribute from class instance.
222
196
  """
223
197
 
224
- return self.__table
198
+ return dict(self.__timers)
225
199
 
226
200
 
227
- @property
228
- def cache(
201
+ def load_children(
229
202
  self,
230
- ) -> _CACHED:
203
+ ) -> None:
204
+ """
205
+ Construct the children instances for the primary class.
231
206
  """
232
- Return the value for the attribute from class instance.
233
207
 
234
- :returns: Value for the attribute from class instance.
208
+ params = self.__params
209
+ timers = self.__timers
210
+
211
+ sqlite = self.__sqlite
212
+ table = self.__table
213
+ group = self.__group
214
+
215
+
216
+ config = params.timers
217
+
218
+
219
+ cursor = sqlite.execute(
220
+ f"""
221
+ select * from {table}
222
+ where "group"="{group}"
223
+ order by "unique" asc
224
+ """) # noqa: LIT003
225
+
226
+ records = cursor.fetchall()
227
+
228
+ for record in records:
229
+
230
+ unique = record[1]
231
+ update = record[2]
232
+
233
+ if unique not in config:
234
+ continue
235
+
236
+ _config = config[unique]
237
+
238
+ _config.start = update
239
+
240
+
241
+ items = config.items()
242
+
243
+ for key, value in items:
244
+
245
+ if key in timers:
246
+
247
+ timer = timers[key]
248
+
249
+ timer.update(
250
+ value.start)
251
+
252
+ continue
253
+
254
+ timer = Timer(
255
+ value.timer,
256
+ start=value.start)
257
+
258
+ timers[key] = timer
259
+
260
+
261
+ self.__timers = timers
262
+
263
+
264
+ def save_children(
265
+ self,
266
+ ) -> None:
267
+ """
268
+ Save the child caches from the attribute into database.
235
269
  """
236
270
 
237
- return dict(self.__cache)
271
+ timers = self.__timers
272
+
273
+ sqlite = self.__sqlite
274
+ table = self.__table
275
+ group = self.__group
276
+
277
+
278
+ insert = tuple[
279
+ str, # group
280
+ str, # unique
281
+ str] # update
282
+
283
+ inserts: list[insert] = []
284
+
285
+ items = timers.items()
286
+
287
+ for unique, timer in items:
288
+
289
+ append = (
290
+ group, unique,
291
+ Times('now').subsec)
292
+
293
+ inserts.append(append)
294
+
295
+
296
+ statement = (
297
+ f"""
298
+ replace into {table}
299
+ ("group", "unique",
300
+ "update")
301
+ values (?, ?, ?)
302
+ """) # noqa: LIT003
303
+
304
+ sqlite.executemany(
305
+ statement,
306
+ tuple(sorted(inserts)))
307
+
308
+ sqlite.commit()
238
309
 
239
310
 
240
311
  def ready(
@@ -245,74 +316,90 @@ class Timers:
245
316
  """
246
317
  Determine whether or not the appropriate time has passed.
247
318
 
248
- .. note::
249
- For performance reasons, this method will not notice
250
- changes within the database unless refreshed first.
251
-
252
- :param unique: Which timer configuration from reference.
319
+ :param unique: Unique identifier for the related child.
253
320
  :param update: Determines whether or not time is updated.
321
+ :returns: Boolean indicating whether enough time passed.
254
322
  """
255
323
 
256
- config = self.__config
257
- caches = self.__cache
324
+ timers = self.__timers
258
325
 
259
- if unique not in caches:
326
+ if unique not in timers:
260
327
  raise ValueError('unique')
261
328
 
262
- cache = caches[unique]
263
- timer = config[unique]
329
+ timer = timers[unique]
264
330
 
265
- ready = cache.since >= timer
331
+ ready = timer.ready(update)
266
332
 
267
- if ready and update:
268
- self.update(unique)
333
+ if ready is True:
334
+ self.save_children()
269
335
 
270
336
  return ready
271
337
 
272
338
 
339
+ def create(
340
+ self,
341
+ unique: str,
342
+ params: 'TimerParams',
343
+ ) -> Timer:
344
+ """
345
+ Create a new timer using the provided input parameters.
346
+
347
+ :param unique: Unique identifier for the related child.
348
+ :param params: Parameters for instantiating the instance.
349
+ :returns: Newly constructed instance of related class.
350
+ """
351
+
352
+ timers = self.params.timers
353
+
354
+ if unique in timers:
355
+ raise ValueError('unique')
356
+
357
+ timers[unique] = params
358
+
359
+ self.load_children()
360
+
361
+ self.save_children()
362
+
363
+ return self.children[unique]
364
+
365
+
273
366
  def update(
274
367
  self,
275
368
  unique: str,
276
- started: Optional[PARSABLE] = None,
369
+ value: Optional[PARSABLE] = None,
277
370
  ) -> None:
278
371
  """
279
- Update the existing timer from mapping within the cache.
372
+ Update the timer from the provided parasable time value.
280
373
 
281
- :param unique: Which timer configuration from reference.
282
- :param started: Override the start time for timer value.
374
+ :param unique: Unique identifier for the related child.
375
+ :param value: Override the time updated for timer value.
283
376
  """
284
377
 
285
- caches = self.__cache
378
+ timers = self.__timers
286
379
 
287
- if unique not in caches:
380
+ if unique not in timers:
288
381
  raise ValueError('unique')
289
382
 
290
- caches[unique] = Times(started)
383
+ timer = timers[unique]
291
384
 
292
- self.save_cache()
385
+ self.save_children()
293
386
 
387
+ return timer.update(value)
294
388
 
295
- def create(
389
+
390
+ def delete(
296
391
  self,
297
392
  unique: str,
298
- minimum: int | float,
299
- started: Optional[PARSABLE] = None,
300
393
  ) -> None:
301
394
  """
302
- Update the existing timer from mapping within the cache.
395
+ Delete the timer from the internal dictionary reference.
303
396
 
304
- :param unique: Which timer configuration from reference.
305
- :param minimum: Determine minimum seconds that must pass.
306
- :param started: Determine when time starts for the timer.
397
+ :param unique: Unique identifier for the related child.
307
398
  """
308
399
 
309
- config = self.__config
310
- caches = self.__cache
400
+ timers = self.__timers
311
401
 
312
- if unique in config:
402
+ if unique not in timers:
313
403
  raise ValueError('unique')
314
404
 
315
- config[unique] = float(minimum)
316
- caches[unique] = Times(started)
317
-
318
- self.save_cache()
405
+ del timers[unique]
encommon/times/times.py CHANGED
@@ -16,10 +16,10 @@ from .common import PARSABLE
16
16
  from .common import STAMP_HUMAN
17
17
  from .common import STAMP_SIMPLE
18
18
  from .common import STAMP_SUBSEC
19
- from .common import findtz
20
- from .common import strftime
21
19
  from .parse import parse_time
22
20
  from .parse import since_time
21
+ from .utils import findtz
22
+ from .utils import strftime
23
23
 
24
24
 
25
25
 
@@ -348,9 +348,9 @@ class Times:
348
348
  self,
349
349
  ) -> float:
350
350
  """
351
- Determine the time in seconds that occured since instance.
351
+ Determine the time in seconds occurring since instance.
352
352
 
353
- :returns: Time in seconds that occured between the values.
353
+ :returns: Time in seconds occurring since the instance.
354
354
  """
355
355
 
356
356
  return since_time(self.__source)
@@ -361,9 +361,9 @@ class Times:
361
361
  self,
362
362
  ) -> float:
363
363
  """
364
- Determine the time in seconds that occured since instance.
364
+ Determine the time in seconds occurring since instance.
365
365
 
366
- :returns: Time in seconds that occured between the values.
366
+ :returns: Time in seconds occurring since the instance.
367
367
  """
368
368
 
369
369
  return since_time(self.__source)
@@ -0,0 +1,148 @@
1
+ """
2
+ Functions and routines associated with Enasis Network Common Library.
3
+
4
+ This file is part of Enasis Network software eco-system. Distribution
5
+ is permitted, for more information consult the project license file.
6
+ """
7
+
8
+
9
+
10
+ from contextlib import suppress
11
+ from datetime import datetime
12
+ from datetime import timezone
13
+ from datetime import tzinfo
14
+ from typing import Any
15
+ from typing import Optional
16
+
17
+ from dateutil.tz import gettz
18
+
19
+
20
+
21
+ def findtz(
22
+ tzname: Optional[str] = None,
23
+ ) -> tzinfo:
24
+ """
25
+ Return the located timezone object for the provided name.
26
+
27
+ Example
28
+ -------
29
+ >>> findtz('US/Central')
30
+ tzfile('/usr/share/zoneinfo/US/Central')
31
+
32
+ :param tzname: Name of the timezone associated to source.
33
+ :returns: Located timezone object for the provided name.
34
+ """
35
+
36
+ if tzname is None:
37
+ return timezone.utc
38
+
39
+ tzinfo = gettz(tzname)
40
+
41
+ if tzinfo is None:
42
+ raise ValueError('tzname')
43
+
44
+ return tzinfo
45
+
46
+
47
+
48
+ def utcdatetime(
49
+ *args: Any,
50
+ **kwargs: Any,
51
+ ) -> datetime:
52
+ """
53
+ Return the instance of datetime within the UTC timezone.
54
+
55
+ .. warning::
56
+ If no arguments are provided, returns current time.
57
+
58
+ Example
59
+ -------
60
+ >>> utcdatetime(1970, 1, 1)
61
+ datetime.datetime(1970, 1, 1, 0...
62
+
63
+ :param args: Positional arguments passed for downstream.
64
+ :param kwargs: Keyword arguments passed for downstream.
65
+ :returns: Instance of datetime within the UTC timezone.
66
+ """
67
+
68
+ tzinfo = timezone.utc
69
+
70
+ if not args and not kwargs:
71
+ return datetime.now(tz=tzinfo)
72
+
73
+ if 'tzinfo' not in kwargs:
74
+ kwargs['tzinfo'] = tzinfo
75
+
76
+ return (
77
+ datetime(*args, **kwargs)
78
+ .astimezone(timezone.utc))
79
+
80
+
81
+
82
+ def strptime(
83
+ source: str,
84
+ formats: str | list[str],
85
+ ) -> datetime:
86
+ """
87
+ Parse provided time value with various supported formats.
88
+
89
+ Example
90
+ -------
91
+ >>> strptime('2023', '%Y')
92
+ datetime.datetime(2023, 1, 1, 0...
93
+
94
+ :param source: Time in various forms that will be parsed.
95
+ :param formats: Various formats compatable with strptime.
96
+ :returns: Python datetime object containing related time.
97
+ """
98
+
99
+ tzinfo = timezone.utc
100
+
101
+ if isinstance(formats, str):
102
+ formats = [formats]
103
+
104
+
105
+ def _strptime(
106
+ format: str,
107
+ ) -> datetime:
108
+
109
+ return (
110
+ datetime
111
+ .strptime(source, format)
112
+ .astimezone(tzinfo))
113
+
114
+
115
+ for format in formats:
116
+
117
+ with suppress(ValueError):
118
+ return _strptime(format)
119
+
120
+
121
+ raise ValueError('invalid')
122
+
123
+
124
+
125
+ def strftime(
126
+ source: datetime,
127
+ format: str,
128
+ ) -> str:
129
+ """
130
+ Return the timestamp string for datetime object provided.
131
+
132
+ .. note::
133
+ This function is extremely pedantic and cosmetic.
134
+
135
+ Example
136
+ -------
137
+ >>> dtime = datetime(2023, 1, 1)
138
+ >>> strftime(dtime, '%Y')
139
+ '2023'
140
+
141
+ :param source: Python datetime instance containing source.
142
+ :param format: Format for the timestamp string returned.
143
+ :returns: Timestamp string for datetime object provided.
144
+ """
145
+
146
+ return (
147
+ datetime
148
+ .strftime(source, format))