clockwork 0.2.0__tar.gz → 0.2.1__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: clockwork
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Toolkit for time-related operations including scheduling, logging, date manipulation, and more.
5
5
  Home-page: https://github.com/zteinck/clockwork
6
6
  License: MIT
@@ -0,0 +1,4 @@
1
+ from .core import *
2
+
3
+ __version__ = '0.2.1'
4
+ __author__ = 'Zachary Einck <zacharyeinck@gmail.com>'
@@ -71,7 +71,8 @@ class DateBase(object):
71
71
  Description
72
72
  ----------
73
73
  Returns the # of days in a month for a given year.
74
- For example, if year=2024 and month=2, 29 days is returned since it's a leap year
74
+ For example, if year=2024 and month=2, 29 days is returned since
75
+ it's a leap year.
75
76
 
76
77
  Parameters
77
78
  ----------
@@ -172,15 +173,18 @@ class DateBase(object):
172
173
 
173
174
  def wrapper(self, weekday, delta=0):
174
175
  '''
175
- returns DateBase object representing the next or last day of the week relative to self. For example self.next('Mon')
176
+ returns DateBase object representing the next or last day of
177
+ the week relative to self. For example self.next('Mon')
176
178
  would return the date of the following monday.
177
179
 
178
180
  Attributes
179
181
  -----------------------
180
182
  weekday : str
181
- Day of the week either fully spelled out or the first three characters (e.g. 'Friday' or 'Fri') not case-sensitive.
183
+ Day of the week either fully spelled out or the first three
184
+ characters (e.g. 'Friday' or 'Fri') not case-sensitive.
182
185
  delta : int
183
- Offset value +/- from the current week (e.g. 0 is the current week and -1 is last week).
186
+ Offset value +/- from the current week (e.g. 0 is the current
187
+ week and -1 is last week).
184
188
  '''
185
189
  day, desired_day = self.dt.weekday(), self.weekdays[weekday.title()]
186
190
  delta += func(self, day - desired_day)
@@ -198,104 +202,132 @@ class DateBase(object):
198
202
  ''' returns normalized instance '''
199
203
  return self.normalize()
200
204
 
205
+
201
206
  @property
202
207
  def d(self):
203
208
  ''' converts datetime.datetime to datetime.date '''
204
209
  return self.datetime.date()
205
210
 
211
+
206
212
  @property
207
213
  def dt(self):
208
214
  ''' datetime alias '''
209
215
  return self.datetime
210
216
 
217
+
211
218
  @property
212
219
  def pandas(self):
213
220
  ''' pandas format '''
214
221
  return pd.to_datetime(self.dt)
215
222
 
223
+
216
224
  @property
217
225
  def ymd(self):
218
226
  ''' string in YYYY-MM-DD format '''
219
227
  return self.str('%Y-%m-%d')
220
228
 
229
+
221
230
  @property
222
231
  def sql_server(self):
223
232
  ''' ymd alias '''
224
233
  return self.ymd
225
234
 
235
+
226
236
  @property
227
237
  def oracle(self):
228
238
  ''' string in DD-%b-YY (e.g. 30-Sep-19) format '''
229
239
  return self.str('%d-%b-%y')
230
240
 
241
+
231
242
  @property
232
243
  def timestamp(self):
233
244
  ''' integer '''
234
245
  return self.to_timestamp(self.dt)
235
246
 
247
+
236
248
  @property
237
249
  def year(self):
238
250
  return self.dt.year
239
251
 
252
+
240
253
  @property
241
254
  def month(self):
242
255
  return self.dt.month
243
256
 
257
+
244
258
  @property
245
259
  def month_name(self):
246
260
  return self.dt.strftime('%B')
247
261
 
262
+
248
263
  @property
249
264
  def day(self):
250
265
  return self.dt.day
251
266
 
267
+
252
268
  @property
253
269
  def yesterday(self):
254
270
  return self - 1
255
271
 
272
+
256
273
  @property
257
274
  def tomorrow(self):
258
275
  return self + 1
259
276
 
277
+
260
278
  @property
261
279
  def month_start(self):
262
280
  ''' return date object representing the first day of the month '''
263
281
  return self.spawn(datetime.datetime(self.year, self.month, 1))
264
282
 
283
+
265
284
  @property
266
285
  def month_end(self):
267
286
  ''' last day of the month as a MonthEnd instance '''
268
287
  return self.last_day_of_month(self.year, self.month)
269
288
 
289
+
290
+ @property
291
+ def is_month_end(self):
292
+ ''' returns True if date aligns with a month-end date '''
293
+ return self.ymd == self.month_end.ymd
294
+
295
+
270
296
  @property
271
297
  def last_business_day_of_month(self):
272
298
  dt = self.month_end
273
299
  dt -= {'Saturday': 1, 'Sunday': 2}.get(dt.weekday, 0)
274
300
  return dt
275
301
 
302
+
276
303
  @property
277
304
  def weekday(self):
278
305
  return self.str('%A')
279
306
 
307
+
280
308
  @property
281
309
  def weekday_short(self):
282
310
  return self.str('%a')
283
311
 
312
+
284
313
  @property
285
314
  def is_weekend(self):
286
315
  ''' returns True if date does not fall on a weekend '''
287
316
  return self.weekday in ('Saturday','Sunday')
288
317
 
318
+
289
319
  @property
290
320
  def is_holiday(self):
291
321
  ''' returns True if date is a U.S. holiday '''
292
322
  return self.ymd in self.holidays
293
323
 
324
+
294
325
  @property
295
326
  def is_business_day(self):
296
327
  ''' returns True if date does not fall on a weekend '''
297
328
  return not (self.is_weekend or self.is_holiday)
298
329
 
330
+
299
331
  @property
300
332
  def is_business_hours(self):
301
333
  ''' returns True if date is within business hours (8am - 9pm) '''
@@ -303,11 +335,13 @@ class DateBase(object):
303
335
  out = self.is_business_day and self.dt >= set_hour(8) and self.dt <= set_hour(21)
304
336
  return out
305
337
 
338
+
306
339
  @property
307
340
  def is_today(self):
308
341
  ''' returns True if date is the current day '''
309
342
  return self.ymd == self.spawn().ymd
310
343
 
344
+
311
345
  @property
312
346
  def holiday(self):
313
347
  ''' returns the current holiday, if applicable '''
@@ -321,51 +355,62 @@ class DateBase(object):
321
355
  def __repr__(self):
322
356
  return str(self)
323
357
 
358
+
324
359
  def __str__(self):
325
360
  components = ['%Y-%m-%d']
326
361
  if self.dt.hour + self.dt.second + self.dt.microsecond > 0:
327
362
  components.append('%I:%M:%S.%f %p')
328
363
  return self.__class__.__name__ + '(%s)' % self.str(' '.join(components))
329
364
 
365
+
330
366
  def __int__(self):
331
367
  return self.timestamp
332
368
 
369
+
333
370
  @Decorators.other_to_dt
334
371
  def __eq__(self, other):
335
372
  return self.dt == other
336
373
 
374
+
337
375
  @Decorators.other_to_dt
338
376
  def __ne__(self, other):
339
377
  return self.dt != other
340
378
 
379
+
341
380
  @Decorators.other_to_dt
342
381
  def __lt__(self, other):
343
382
  return self.dt < other
344
383
 
384
+
345
385
  @Decorators.other_to_dt
346
386
  def __gt__(self, other):
347
387
  return self.dt > other
348
388
 
389
+
349
390
  @Decorators.other_to_dt
350
391
  def __le__(self, other):
351
392
  return self.dt <= other
352
393
 
394
+
353
395
  @Decorators.other_to_dt
354
396
  def __ge__(self, other):
355
397
  return self.dt >= other
356
398
 
399
+
357
400
  @Decorators.arithmetic_other
358
401
  def __add__(self, other):
359
- ''' if other is date-like then implements default behavior for adding datetimes otherwise
360
- other is treated as timedelta '''
402
+ ''' if other is date-like then implements default behavior for adding
403
+ datetimes otherwise other is treated as timedelta '''
361
404
  return self.dt + other
362
405
 
406
+
363
407
  @Decorators.arithmetic_other
364
408
  def __sub__(self, other):
365
- ''' if other is date-like then implements default behavior for subtracting datetimes otherwise
366
- other is treated as timedelta '''
409
+ ''' if other is date-like then implements default behavior for subtracting
410
+ datetimes otherwise other is treated as timedelta '''
367
411
  return self.dt - other
368
412
 
413
+
369
414
  @Decorators.other_to_dt
370
415
  def __contains__(self, item):
371
416
  return self.normalize(item).dt <= self.dt < (self.normalize(item) + 1).dt
@@ -447,5 +492,6 @@ class DateBase(object):
447
492
 
448
493
  @Decorators.spawn
449
494
  def normalize(self):
450
- ''' the time component (hours, minutes, seconds, microseconds) is set to zero (midnight) '''
495
+ ''' the time component (hours, minutes, seconds, microseconds) is set to
496
+ zero (midnight) '''
451
497
  return datetime.datetime(self.year, self.month, self.day)
@@ -9,7 +9,8 @@ class MonthEnd(DateBase):
9
9
  #╰-------------------------------------------------------------------------╯
10
10
 
11
11
  def __init__(self, dt):
12
- super().__init__(dt.replace(day=self.n_days_in_month(dt.year, dt.month)))
12
+ day = self.n_days_in_month(dt.year, dt.month)
13
+ super().__init__(dt.replace(day=day))
13
14
 
14
15
 
15
16
  #╭-------------------------------------------------------------------------╮
@@ -20,21 +21,35 @@ class MonthEnd(DateBase):
20
21
  def long(self):
21
22
  return self.str('%Y-%m-%d')
22
23
 
24
+
23
25
  @property
24
26
  def compact(self):
25
27
  return self.str('%Y-%m')
26
28
 
29
+
27
30
  @property
28
31
  def short(self):
29
32
  return self.str('%b')
30
33
 
34
+
35
+ @property
36
+ def is_year_end(self):
37
+ return self.month == 12
38
+
39
+
40
+ @property
41
+ def is_quarter_end(self):
42
+ return self.month in {3, 6, 9, 12}
43
+
44
+
31
45
  @property
32
46
  def last_quarter_end(self):
33
47
  ''' returns most recent quarter end '''
34
48
  delta = 0
35
49
  while True:
36
- obj = self.offset(delta=delta)
37
- if hasattr(obj, 'quarter'): return obj
50
+ me = self.offset(delta=delta)
51
+ if me.is_quarter_end:
52
+ return me.to_quarter_end()
38
53
  delta -= 1
39
54
 
40
55
 
@@ -42,6 +57,17 @@ class MonthEnd(DateBase):
42
57
  #| Instance Methods |
43
58
  #╰-------------------------------------------------------------------------╯
44
59
 
60
+ def to_quarter_end(self):
61
+ from ._quarter_end import QuarterEnd
62
+ if self.is_quarter_end:
63
+ return QuarterEnd(self.dt)
64
+ else:
65
+ raise ValueError(
66
+ "Month-end date does not align with a quarter-end "
67
+ f"date: '{self.ymd}'"
68
+ )
69
+
70
+
45
71
  def offset(self, delta):
46
72
  ''' returns the month end 'delta' months away from the instance '''
47
73
  if not isinstance(delta, int):
@@ -4,13 +4,18 @@ from ._month_end import MonthEnd
4
4
 
5
5
  class QuarterEnd(MonthEnd):
6
6
 
7
+ #╭-------------------------------------------------------------------------╮
8
+ #| Class Attributes |
9
+ #╰-------------------------------------------------------------------------╯
10
+ scheme = (3, 6, 9, 12)
11
+
12
+
7
13
  #╭-------------------------------------------------------------------------╮
8
14
  #| Initialize Instance |
9
15
  #╰-------------------------------------------------------------------------╯
10
16
 
11
- def __init__(self, dt, qtr, *args, **kwargs):
17
+ def __init__(self, dt):
12
18
  super().__init__(dt)
13
- self.qtr = qtr
14
19
 
15
20
 
16
21
  #╭-------------------------------------------------------------------------╮
@@ -21,17 +26,26 @@ class QuarterEnd(MonthEnd):
21
26
  def long(self):
22
27
  return f'{self.year}Q{self.qtr}'
23
28
 
29
+
24
30
  @property
25
31
  def compact(self):
26
32
  return f'{self.qtr}Q' + self.str('%y')
27
33
 
34
+
28
35
  @property
29
36
  def short(self):
30
37
  return f'Q{self.qtr}'
31
38
 
39
+
32
40
  @property
33
41
  def quarter(self):
34
- return self.qtr
42
+ return int(self.scheme.index(self.month) + 1)
43
+
44
+
45
+ @property
46
+ def qtr(self):
47
+ ''' quarter alias '''
48
+ return self.quarter
35
49
 
36
50
 
37
51
  #╭-------------------------------------------------------------------------╮
@@ -49,4 +63,4 @@ class QuarterEnd(MonthEnd):
49
63
  if delta == 0: return self
50
64
  dt = self.shift(months=delta * 3).dt
51
65
  qtr = ((self.qtr - 1 + delta) % 4) + 1
52
- return self.__class__(dt, qtr)
66
+ return self.__class__(dt)
@@ -8,10 +8,6 @@ from ._month_end import MonthEnd
8
8
 
9
9
 
10
10
 
11
- #╭-------------------------------------------------------------------------╮
12
- #| Functions |
13
- #╰-------------------------------------------------------------------------╯
14
-
15
11
  def Date(arg=None, normalize=False, format=None, week_offset=0):
16
12
  '''
17
13
  Description
@@ -21,26 +17,31 @@ def Date(arg=None, normalize=False, format=None, week_offset=0):
21
17
  Parameters
22
18
  ------------
23
19
  arg : str | object
24
- object to convert to DateBase object. Currently supported formats include:
20
+ object to convert to DateBase object. Currently supported formats
21
+ include:
25
22
  • None (the current date and time will be used)
26
23
  • pandas._libs.tslibs.timestamps.Timestamp
27
24
  • datetime.datetime
28
25
  • datetime.date
29
26
  • integer or float (in seconds)
30
27
  • string
31
- ► day of the week fully spelled out or first 3 letters (not case sensitive)
32
- (e.g. 'Monday', 'monday', 'mon')
28
+ ► day of the week fully spelled out or first 3 letters
29
+ (not case sensitive) (e.g. 'Monday', 'monday', 'mon')
33
30
  ► quarter in #QYY, YYYYQ#, or Q# format
34
31
  ► any string format supported by pd.to_datetime
35
32
  • DateBase object or DateBase polymorphism
36
33
  normalize : bool
37
- if True, only the year, month, and day are retained (hours, minutes, seconds, microseconds are set to zero)
34
+ if True, only the year, month, and day are retained (hours, minutes,
35
+ seconds, microseconds are set to zero)
38
36
  format : str
39
- if 'arg' is a string, this format is used to parse it (e.g. '%Y%m%d %H%S').
37
+ if 'arg' is a string, this format is used to parse it
38
+ (e.g. '%Y%m%d %H%S').
40
39
  week_offset : int
41
- by default, if a day of the week is supplied (e.g. 'Monday') then the date returned will be that day of the week
42
- for the current week. This argument is used to override this behavior by shifting the week backwards or forwards
43
- (e.g. if arg='Monday' and week_offset=-1 then the Monday of last week will be returned).
40
+ by default, if a day of the week is supplied (e.g. 'Monday') then
41
+ the date returned will be that day of the week for the current week.
42
+ This argument is used to override this behavior by shifting the week
43
+ backwards or forwards (e.g. if arg='Monday' and week_offset=-1 then
44
+ the Monday of last week will be returned).
44
45
 
45
46
  Returns
46
47
  ------------
@@ -48,7 +49,8 @@ def Date(arg=None, normalize=False, format=None, week_offset=0):
48
49
  DateBase object or polymorphism
49
50
  '''
50
51
 
51
- qtr_map = {k: i for i,k in enumerate([(3,31), (6,30), (9,30), (12,31)], 1)}
52
+ qtr_scheme = [(3, 31), (6, 30), (9, 30), (12, 31)]
53
+ qtr_map = {k: i for i, k in enumerate(qtr_scheme, 1)}
52
54
 
53
55
  def qtr_label_to_dt(x):
54
56
  ''' attempts to convert quarter expressed as string to datetime.
@@ -74,7 +76,10 @@ def Date(arg=None, normalize=False, format=None, week_offset=0):
74
76
 
75
77
  if format is not None:
76
78
  if not isinstance(arg, str):
77
- raise TypeError(f"When 'format' argument is not None, 'arg' must be a string, not {type(arg)}.")
79
+ raise TypeError(
80
+ "When 'format' argument is not None, 'arg' must be a "
81
+ f"string, not {type(arg).__name__}."
82
+ )
78
83
  arg = DateBase.to_datetime(arg, format=format)
79
84
  return Date(arg, normalize=normalize)
80
85
 
@@ -102,22 +107,21 @@ def Date(arg=None, normalize=False, format=None, week_offset=0):
102
107
  else:
103
108
  raise ValueError(f'date argument {arg} of type {type(arg)} is not supported.')
104
109
 
105
- if normalize: dt = datetime.datetime(dt.year, dt.month, dt.day)
110
+ if normalize:
111
+ dt = datetime.datetime(dt.year, dt.month, dt.day)
106
112
 
107
113
  if dt.day == DateBase.n_days_in_month(dt.year, dt.month):
108
114
  qtr = qtr_map.get((dt.month, dt.day))
109
- return QuarterEnd(dt, qtr) if qtr else MonthEnd(dt)
115
+ return QuarterEnd(dt) if qtr else MonthEnd(dt)
110
116
  else:
111
117
  return DateBase(dt)
112
118
 
113
119
 
114
-
115
120
  def day_of_week(day, delta=0):
116
121
  ''' see DateBase.first_last decorator for documentation '''
117
122
  return Date(normalize=True).last(day, delta)
118
123
 
119
124
 
120
-
121
125
  def month_end(delta=0):
122
126
  '''
123
127
  Description
@@ -127,21 +131,29 @@ def month_end(delta=0):
127
131
  Parameters
128
132
  ------------
129
133
  delta : int | str | DateBase
130
- Offset value +/- from the most recent month end (e.g. 0 is the most recent month end and -1 is the 2nd most recent month end).
134
+ Offset value +/- from the most recent month end (e.g. 0 is the most
135
+ recent month end and -1 is the 2nd most recent month end).
131
136
 
132
137
  Returns
133
138
  ------------
134
139
  clockwork.MonthEnd object
135
140
  '''
136
- if isinstance(delta, (DateBase, str)): return Date(delta)
137
- y, m = divmod(datetime.date.today().year * 12 + datetime.date.today().month + delta - 1, 12)
141
+ if isinstance(delta, (DateBase, str)):
142
+ out = Date(delta)
143
+ if not isinstance(out, MonthEnd):
144
+ raise ValueError(
145
+ f"delta '{delta}' did not yield a month-end object: {out}"
146
+ )
147
+ return out
148
+
149
+ today = datetime.date.today()
150
+ y, m = divmod(today.year * 12 + today.month + delta - 1, 12)
138
151
  if m == 0:
139
152
  y -= 1
140
153
  m = 12
141
154
  return MonthEnd(datetime.datetime(y, m, 1))
142
155
 
143
156
 
144
-
145
157
  def quarter_end(delta=0, scheme=None):
146
158
  '''
147
159
  Description
@@ -151,10 +163,11 @@ def quarter_end(delta=0, scheme=None):
151
163
  Parameters
152
164
  ------------
153
165
  delta : int | str | DateBase
154
- clockwork.date() -> 'date' argument.
155
- Integer values are treated as offsets +/- from the most recent quarter end (e.g. 0 is the most recent quarter
156
- end and -1 is the 2nd most recent quarter end).
157
- String values represent specific quarters. Acceptable formats include: '#QYY' or 'YYYYQ#'
166
+ Integer values are treated as offsets (+/-) from the most recent quarter
167
+ end (e.g. 0 is the most recent quarter end and -1 is the 2nd most recent
168
+ quarter end).
169
+ String values represent specific quarters. Acceptable formats include:
170
+ '#QYY' or 'YYYYQ#'
158
171
  scheme : tuple
159
172
  Tuple listing the quarter end months. Defaults to calendar year-end.
160
173
 
@@ -162,18 +175,29 @@ def quarter_end(delta=0, scheme=None):
162
175
  ------------
163
176
  clockwork.QuarterEnd object
164
177
  '''
165
- if isinstance(delta, (DateBase, str)): return Date(delta)
166
- if scheme is not None: raise NotImplementedError
167
- scheme = (3, 6, 9, 12)
178
+ if scheme is not None:
179
+ raise NotImplementedError(
180
+ 'Only default quarter-end months are currently supported'
181
+ )
182
+
183
+ if isinstance(delta, (DateBase, str)):
184
+ out = Date(delta)
185
+ if not isinstance(out, QuarterEnd):
186
+ raise ValueError(
187
+ f"delta '{delta}' did not yield a quarter-end object: {out}"
188
+ )
189
+ return out
190
+
191
+ scheme = QuarterEnd.scheme
168
192
  today = datetime.datetime.now()
169
193
  cy, cm, cd = today.year, today.month, today.day
170
194
  if cd <= DateBase.n_days_in_month(cy, cm): cm -= 1
171
195
  candidates = [((cy * 12) + m) + (delta * 3) - 1 for m in scheme]
172
- candidates.insert(0,((cy - 1) * 12) + scheme[-1] + (delta * 3) - 1)
173
- y, m = divmod(candidates[np.digitize(((cy * 12) + cm + (delta * 3)), candidates, right=True) - 1], 12)
196
+ candidates.insert(0, ((cy - 1) * 12) + scheme[-1] + (delta * 3) - 1)
197
+ index = np.digitize(((cy * 12) + cm + (delta * 3)), candidates, right=True) - 1
198
+ y, m = divmod(candidates[index], 12)
174
199
  m += 1
175
- return QuarterEnd(dt=datetime.datetime(y, m, 1), qtr=scheme.index(m) + 1)
176
-
200
+ return QuarterEnd(datetime.datetime(y, m, 1))
177
201
 
178
202
 
179
203
  def year_end(delta=0, **kwargs):
@@ -183,7 +207,6 @@ def year_end(delta=0, **kwargs):
183
207
  return quarter_end(delta, **kwargs)
184
208
 
185
209
 
186
-
187
210
  #╭-------------------------------------------------------------------------╮
188
211
  #| Assign Class Attribute |
189
212
  #╰-------------------------------------------------------------------------╯
@@ -0,0 +1,19 @@
1
+ import time
2
+
3
+ from .utils import elapsed_time, add_border
4
+
5
+
6
+
7
+ def action_timer(func):
8
+
9
+ def wrapper(*args, **kwargs):
10
+ start_time = time.time()
11
+ print(add_border(func.__name__, width=75))
12
+ print()
13
+ out = func(*args, **kwargs)
14
+ et = elapsed_time(time.time() - start_time)
15
+ print(add_border(f'{func.__name__} complete in {et}', width=75))
16
+ print()
17
+ return out
18
+
19
+ return wrapper
@@ -13,8 +13,8 @@ class Task(object):
13
13
  '''
14
14
  Description
15
15
  --------------------
16
- job object designed to be used in conjunction with TaskScheduler as the 'job' argument
17
- in self._run_job()
16
+ job object designed to be used in conjunction with TaskScheduler as the
17
+ 'job' argument in self._run_job()
18
18
 
19
19
  Class Attributes
20
20
  --------------------
@@ -23,7 +23,8 @@ class Task(object):
23
23
  verbose : bool
24
24
  If True, class content is printed to console.
25
25
  disable_print : bool
26
- If True, printing is suppressed while func runs. Must be True if verbose is True.
26
+ If True, printing is suppressed while func runs. Must be True if verbose
27
+ is True.
27
28
  cascade_status : str
28
29
  cascade status
29
30
 
@@ -44,24 +45,30 @@ class Task(object):
44
45
  cancel_on_failure : bool
45
46
  If True, the job will be cancelled if it raises an exception.
46
47
  cancel_on_completion : bool
47
- If True, the job will be cancelled if it completed successfully (i.e. job will run only once).
48
+ If True, the job will be cancelled if it completed successfully
49
+ (i.e. job will run only once).
48
50
  notify_on_failure : bool
49
- If True, an email notification is sent to my inbox that includes the traceback. cancel_on_failure
50
- and cascade are automatically set to True if this argument is True (prevents endless spam).
51
+ If True, an email notification is sent to my inbox that includes the
52
+ traceback. cancel_on_failure and cascade are automatically set to True
53
+ if this argument is True (prevents endless spam).
51
54
  restrict_to_business_hours : bool
52
- If True, the job will only execute during business hours. This is a more restrictive version of restrict_to_business_days.
55
+ If True, the job will only execute during business hours. This is a more
56
+ restrictive version of restrict_to_business_days.
53
57
  restrict_to_business_days : bool
54
58
  If True, the job will only execute during weekdays (i.e. not weekends).
55
59
  cascade : bool
56
- If True, when the job (denoted by the 'name' argument) is reflected multiple times in the jobs table
57
- due to having multiple 'at' values, changes in activiation in one will cascade to all others. For example,
58
- consider the job named 'my job' which is scheduled at 8:00 AM and 5:00 PM that is cancelled on completion.
59
- If the 8:00 AM completes successfully then the job with that 'at' time will be set to inactive and have its
60
- status updated in the table accordingly. Under default behavior, the 5:00 PM run will be unaffected by the
61
- completion of the 8:00 AM run, however, if cascade is set to True then the 5:00 PM run will also receive
62
- the same updates.
60
+ If True, when the job (denoted by the 'name' argument) is reflected
61
+ multiple times in the jobs table due to having multiple 'at' values,
62
+ changes in activiation in one will cascade to all others. For example,
63
+ consider the job named 'my job' which is scheduled at 8:00 AM and 5:00
64
+ PM that is cancelled on completion. If the 8:00 AM completes successfully
65
+ then the job with that 'at' time will be set to inactive and have its status
66
+ updated in the table accordingly. Under default behavior, the 5:00 PM run
67
+ will be unaffected by the completion of the 8:00 AM run, however, if cascade
68
+ is set to True then the 5:00 PM run will also receive the same updates.
63
69
  attempts : int
64
- If greater than 1, the job will be attempted this number of times before being cancelled.
70
+ If greater than 1, the job will be attempted this number of times before
71
+ being cancelled.
65
72
  status : str | None
66
73
  current status of the job
67
74
  '''
@@ -6,9 +6,10 @@ class FileMonitor(object):
6
6
  '''
7
7
  Description
8
8
  --------------------
9
- Monitors a folder for new files and passes latest and 2nd latest file names to
10
- user-defined function. Class is intended to be used in conjunction with TaskMaster
11
- as a func argument which allows for file monitoring at regular intervals.
9
+ Monitors a folder for new files and passes latest and 2nd latest file
10
+ names to user-defined function. Class is intended to be used in conjunction
11
+ with TaskMaster as a func argument which allows for file monitoring at regular
12
+ intervals.
12
13
 
13
14
  Class Attributes
14
15
  --------------------
@@ -17,8 +18,8 @@ class FileMonitor(object):
17
18
  Instance Attributes
18
19
  --------------------
19
20
  func : func
20
- Custom function that takes the latest and second-latest file names in a folder as
21
- the first and second arguments, respectively.
21
+ Custom function that takes the latest and second-latest file names in a
22
+ folder as the first and second arguments, respectively.
22
23
  folder : Folder object
23
24
  folder to monitor for new files.
24
25
  filter_kwargs : dict
@@ -8,13 +8,17 @@ from pathpilot import Folder
8
8
  #╰-------------------------------------------------------------------------╯
9
9
 
10
10
  class CustomLogFormatter(logging.Formatter):
11
- ''' the default implementation of logging.Formatter does not allow timestamps to be formatted how I want '''
11
+ ''' the default implementation of logging.Formatter does not allow
12
+ timestamps to be formatted how I want '''
12
13
 
13
14
  converter = datetime.datetime.fromtimestamp
14
15
 
15
16
  def formatTime(self, record, datefmt=None):
16
- if datefmt is not None: raise TypeError('datefmt argument must be None')
17
- return self.converter(record.created).strftime('%Y-%m-%d %I:%M:%S.{} %p').format('%03d' % record.msecs)
17
+ if datefmt is not None:
18
+ raise TypeError('datefmt argument must be None')
19
+ return self.converter(record.created)\
20
+ .strftime('%Y-%m-%d %I:%M:%S.{} %p')\
21
+ .format('%03d' % record.msecs)
18
22
 
19
23
 
20
24
 
@@ -55,7 +59,9 @@ class Logger(object):
55
59
  #formatter = CustomLogFormatter(fmt='%(asctime)s %(name)s %(levelname)s %(message)s')
56
60
 
57
61
  # create file handler which logs even debug messages
58
- self.file = Folder().parent.join('Data', 'Logger', read_only=False).join(f'{name}.log').path
62
+ self.file = Folder().parent\
63
+ .join('Data', 'Logger', read_only=False)\
64
+ .join(f'{name}.log').path
59
65
  if clear: self.clear()
60
66
  fh = logging.FileHandler(self.file)
61
67
  fh.setLevel(logging.DEBUG)
@@ -92,7 +98,6 @@ class Logger(object):
92
98
  open(self.file, 'w').close()
93
99
 
94
100
 
95
-
96
101
  #╭-------------------------------------------------------------------------╮
97
102
  #| Functions |
98
103
  #╰-------------------------------------------------------------------------╯
@@ -18,8 +18,8 @@ class TaskMaster(object):
18
18
  '''
19
19
  Description
20
20
  --------------------
21
- Class provides a user-friendly means of using the TaskScheduler implementation of
22
- schedule.Scheduler to schedule SmartJobs
21
+ Class provides a user-friendly means of using the TaskScheduler
22
+ implementation of schedule.Scheduler to schedule SmartJobs
23
23
 
24
24
  Class Attributes
25
25
  --------------------
@@ -75,9 +75,11 @@ class TaskMaster(object):
75
75
  Task.at
76
76
  active : binary
77
77
  If 1, the job is active and able to be scheduled.
78
- If 0, the job is inactive and unable to be scheduled. This value is obtained via one of the following avenues
78
+ If 0, the job is inactive and unable to be scheduled. This value is
79
+ obtained via one of the following avenues
79
80
  1) job completed succesfully and was cancelled
80
- 2) job obained this status via cascade from a job that satisfied criteria in 1)
81
+ 2) job obained this status via cascade from a job that satisfied
82
+ criteria in 1)
81
83
  3) job was manually set inactive via TaskMaster.set_inactive method
82
84
  expiry : str
83
85
  Task.expiry
@@ -105,7 +107,16 @@ class TaskMaster(object):
105
107
 
106
108
 
107
109
  @classmethod
108
- def add(cls, func, every=None, at=None, interval=1, start=None, expiry=None, **kwargs):
110
+ def add(
111
+ cls,
112
+ func,
113
+ every=None,
114
+ at=None,
115
+ interval=1,
116
+ start=None,
117
+ expiry=None,
118
+ **kwargs
119
+ ):
109
120
  '''
110
121
  Description
111
122
  ------------
@@ -124,20 +135,23 @@ class TaskMaster(object):
124
135
  func : func
125
136
  Task func argument
126
137
  every : str
127
- string representation of schedule.Job property (e.g. 'minutes', 'hour', 'day' etc.).
128
- If None and cancel_on_completion kwarg is True, every and interval will be set to
129
- 'second' and 1, respectively, so that the job will run ASAP once the 'start' criteria
130
- has been met (if applicable).
138
+ string representation of schedule.Job property (e.g. 'minutes',
139
+ 'hour', 'day' etc.).
140
+ If None and cancel_on_completion kwarg is True, every and interval
141
+ will be set to 'second' and 1, respectively, so that the job will
142
+ run ASAP once the 'start' criteria has been met (if applicable).
131
143
  at : str | iter
132
- time_str argument passed to schedule.Job.at(time_str). Times may be passed in
133
- '%I:%M%p' format (e.g ['06:15 AM', '12:15 PM', '06:15 PM']). If argument is an
134
- iterable then the job will be scheduled at each constituent time.
144
+ time_str argument passed to schedule.Job.at(time_str). Times may be
145
+ passed in '%I:%M%p' format (e.g ['06:15 AM', '12:15 PM', '06:15 PM']).
146
+ If argument is an iterable then the job will be scheduled at each
147
+ constituent time.
135
148
  interval : int
136
149
  schedule.Scheduler.every interval argument
137
150
  start : Date
138
151
  If not None, job will be not be added until this datetime
139
152
  expiry : Date
140
- If not None, job will be set inactive and stop running after this datetime
153
+ If not None, job will be set inactive and stop running after this
154
+ datetime
141
155
  kwargs : keyword arguments
142
156
  keyword arguments passed to Task.__init__
143
157
 
@@ -147,7 +161,8 @@ class TaskMaster(object):
147
161
  '''
148
162
 
149
163
  if not hasattr(cls, 'db'):
150
- cls.db = Folder().parent.join('Data', 'SQLite', read_only=False).join('taskmaster.sqlite')
164
+ cls.db = Folder().parent.join('Data', 'SQLite', read_only=False)\
165
+ .join('taskmaster.sqlite')
151
166
  cls.db.connect()
152
167
  cls.db.enable_foreign_keys()
153
168
 
@@ -215,7 +230,6 @@ class TaskMaster(object):
215
230
  return True
216
231
 
217
232
 
218
-
219
233
  #╭-------------------------------------------------------------------------╮
220
234
  #| Assign Class Attribute |
221
235
  #╰-------------------------------------------------------------------------╯
@@ -1,16 +1,11 @@
1
1
  import re
2
- import time
3
2
  from textwrap import wrap as wrap_text
4
3
 
5
4
 
6
5
 
7
- #╭-------------------------------------------------------------------------╮
8
- #| Functions |
9
- #╰-------------------------------------------------------------------------╯
10
-
11
6
  def elapsed_time(seconds):
12
7
  out = []
13
- for k,v in [('days', 86400), ('hours', 3600), ('minutes', 60)]:
8
+ for k, v in [('days', 86400), ('hours', 3600), ('minutes', 60)]:
14
9
  count = int(seconds / v)
15
10
  if count > 0:
16
11
  out.append(f'{count} {k}')
@@ -19,20 +14,6 @@ def elapsed_time(seconds):
19
14
  return ', '.join(out)
20
15
 
21
16
 
22
- def action_timer(func):
23
-
24
- def wrapper(*args, **kwargs):
25
- start_time = time.time()
26
- print(add_border(func.__name__, width=75))
27
- print()
28
- out = func(*args, **kwargs)
29
- print(add_border(f'{func.__name__} complete in {elapsed_time(time.time() - start_time)}', width=75))
30
- print()
31
- return out
32
-
33
- return wrapper
34
-
35
-
36
17
  def month_year_iter(start_month, start_year, end_month=None, end_year=None, rng=None, step=0):
37
18
 
38
19
  start = 12 * start_year + start_month - 1
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "clockwork"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Toolkit for time-related operations including scheduling, logging, date manipulation, and more."
5
5
  authors = ["Zachary Einck <zacharyeinck@gmail.com>"]
6
6
  license = "MIT"
@@ -1,4 +0,0 @@
1
- from .core import *
2
-
3
- __version__ = '0.2.0'
4
- __author__ = 'Zachary Einck <zacharyeinck@gmail.com>'
File without changes
File without changes