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