clockwork 0.3.3__tar.gz → 0.3.5__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.3.3
3
+ Version: 0.3.5
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
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Requires-Dist: holidays
18
18
  Requires-Dist: numpy
19
- Requires-Dist: oddments (>=0.3.0)
19
+ Requires-Dist: oddments (>=0.3.1)
20
20
  Requires-Dist: pandas
21
21
  Requires-Dist: schedule
22
22
  Project-URL: Repository, https://github.com/zteinck/clockwork
@@ -5,8 +5,8 @@ from .decorators import action_timer
5
5
 
6
6
  from .utils import (
7
7
  format_elapsed_seconds,
8
- date_format_to_regex,
8
+ temporal_format_to_regex,
9
9
  )
10
10
 
11
- __version__ = '0.3.3'
11
+ __version__ = '0.3.5'
12
12
  __author__ = 'Zachary Einck <zacharyeinck@gmail.com>'
@@ -4,7 +4,7 @@ from contextlib import redirect_stdout
4
4
  from schedule import CancelJob
5
5
 
6
6
  from ..timestamp import Timestamp
7
- from ..utils import elapsed_time
7
+ from ..utils import format_elapsed_seconds
8
8
  from .utils import PrerequisiteError, ContinueFailedJob
9
9
 
10
10
 
@@ -22,8 +22,8 @@ class Task(object):
22
22
  verbose : bool
23
23
  If True, class content is printed to console.
24
24
  disable_print : bool
25
- If True, printing is suppressed while func runs. Must be True if verbose
26
- is True.
25
+ If True, printing is suppressed while func runs. Must be True if
26
+ verbose is True.
27
27
  cascade_status : str
28
28
  cascade status
29
29
 
@@ -52,8 +52,8 @@ class Task(object):
52
52
  traceback. cancel_on_failure and cascade are automatically set to True
53
53
  if this argument is True (prevents endless spam).
54
54
  restrict_to_business_hours : bool
55
- If True, the job will only execute during business hours. This is a more
56
- restrictive version of restrict_to_business_days.
55
+ If True, the job will only execute during business hours. This is a
56
+ more restrictive version of restrict_to_business_days.
57
57
  restrict_to_business_days : bool
58
58
  If True, the job will only execute during weekdays (i.e. not weekends).
59
59
  cascade : bool
@@ -61,14 +61,15 @@ class Task(object):
61
61
  multiple times in the jobs table due to having multiple 'at' values,
62
62
  changes in activiation in one will cascade to all others. For example,
63
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.
64
+ PM that is cancelled on completion. If the 8:00 AM completes
65
+ successfully then the job with that 'at' time will be set to inactive
66
+ and have its status updated in the table accordingly. Under default
67
+ behavior, the 5:00 PM run will be unaffected by the completion of the
68
+ 8:00 AM run, however, if cascade is set to True then the 5:00 PM run
69
+ will also receive the same updates.
69
70
  attempts : int
70
- If greater than 1, the job will be attempted this number of times before
71
- being cancelled.
71
+ If greater than 1, the job will be attempted this number of times
72
+ before being cancelled.
72
73
  status : str | None
73
74
  current status of the job
74
75
  '''
@@ -161,7 +162,10 @@ class Task(object):
161
162
 
162
163
  def update_status(status, set_inactive=False):
163
164
  self.status = status
164
- if logger: logger.info(f"{self.name} status update: '{self.status}'")
165
+
166
+ if logger:
167
+ logger.info(f"{self.name} status update: '{self.status}'")
168
+
165
169
  self.update_table(active=0 if set_inactive else 1)
166
170
 
167
171
  if self.expiry and self.expiry < Timestamp():
@@ -188,11 +192,17 @@ class Task(object):
188
192
  print(self.name, end='')
189
193
  print(f' @ {Timestamp()} ->', end=' ')
190
194
 
191
- if self.restrict_to_business_hours and not Timestamp().is_business_hours:
192
- raise PrerequisiteError('cannot execute outside of business hours')
195
+ if self.restrict_to_business_hours \
196
+ and not Timestamp().is_business_hours:
197
+ raise PrerequisiteError(
198
+ 'cannot execute outside of business hours'
199
+ )
193
200
 
194
- if self.restrict_to_business_days and not Timestamp().is_business_day:
195
- raise PrerequisiteError('cannot execute during the weekend')
201
+ if self.restrict_to_business_days \
202
+ and not Timestamp().is_business_day:
203
+ raise PrerequisiteError(
204
+ 'cannot execute during the weekend'
205
+ )
196
206
 
197
207
  if self.disable_print:
198
208
  with redirect_stdout(open(os.devnull, 'w')):
@@ -201,10 +211,16 @@ class Task(object):
201
211
  out = self.func(*self.args, **self.kwargs)
202
212
 
203
213
  if self.verbose:
204
- print(f'Complete {elapsed_time(time.time() - start)}', end='')
205
- print(' (Job Cancelled)') if self.cancel_on_completion else print('')
214
+ elapsed = format_elapsed_seconds(time.time() - start)
215
+ print(f'Complete {elapsed}' , end='')
216
+ print(
217
+ ' (Job Cancelled)'
218
+ if self.cancel_on_completion
219
+ else ''
220
+ )
206
221
 
207
- if logger: logger.info(f'{self.name} complete')
222
+ if logger:
223
+ logger.info(f'{self.name} complete')
208
224
 
209
225
  if self.cancel_on_completion:
210
226
  update_status('cancelled on completion', set_inactive=True)
@@ -240,4 +256,8 @@ class Task(object):
240
256
  if self.notify_on_failure:
241
257
  self.send_email_notification()
242
258
 
243
- return CancelJob if self.cancel_on_failure else ContinueFailedJob
259
+ return (
260
+ CancelJob
261
+ if self.cancel_on_failure
262
+ else ContinueFailedJob
263
+ )
@@ -53,8 +53,9 @@ class Logger(object):
53
53
 
54
54
  # create custom formatter
55
55
  # https://docs.python.org/3/library/logging.html#logrecord-attributes
56
- formatter = CustomLogFormatter(fmt='%(asctime)s %(levelname)s %(message)s')
57
- # formatter = CustomLogFormatter(fmt='%(asctime)s %(name)s %(levelname)s %(message)s')
56
+ formatter = CustomLogFormatter(
57
+ fmt='%(asctime)s %(levelname)s %(message)s'
58
+ )
58
59
 
59
60
  # create file handler which logs even debug messages
60
61
  self.file = self.folder.join(f'{name}.log', read_only=False).path
@@ -78,7 +78,8 @@ class TaskMaster(object):
78
78
  1) job completed succesfully and was cancelled
79
79
  2) job obained this status via cascade from a job that satisfied
80
80
  criteria in 1)
81
- 3) job was manually set inactive via TaskMaster.set_inactive method
81
+ 3) job was manually set inactive via TaskMaster.set_inactive
82
+ method
82
83
  expiry : str
83
84
  Task.expiry
84
85
  status : str
@@ -139,8 +140,8 @@ class TaskMaster(object):
139
140
  will be set to 'second' and 1, respectively, so that the job will
140
141
  run ASAP once the 'start' criteria has been met (if applicable).
141
142
  at : str | iter
142
- time_str argument passed to schedule.Job.at(time_str). Times may be
143
- passed in '%I:%M%p' format (e.g ['06:15 AM', '12:15 PM', '06:15 PM']).
143
+ time_string argument passed to schedule.Job.at(time_string). Times may be
144
+ passed in '%I:%M%p' format (e.g ['06:15 AM', '12:15 PM']).
144
145
  If argument is an iterable then the job will be scheduled at each
145
146
  constituent time.
146
147
  interval : int
@@ -185,12 +186,13 @@ class TaskMaster(object):
185
186
  job = getattr(cls.scheduler.every(interval), every)
186
187
  if at is not None:
187
188
  try:
188
- time_str = datetime.datetime.strptime(
189
- at.upper().replace(' ',''), '%I:%M%p'
189
+ time_string = datetime.datetime.strptime(
190
+ at.upper().replace(' ', ''),
191
+ '%I:%M%p'
190
192
  ).strftime('%H:%M')
191
193
  except:
192
- time_str = at
193
- job = job.at(time_str)
194
+ time_string = at
195
+ job = job.at(time_string)
194
196
  else:
195
197
  at = 'N/A'
196
198
 
@@ -5,9 +5,9 @@ import holidays as hd
5
5
  import pandas as pd
6
6
  import numpy as np
7
7
  import oddments as odd
8
- from copy import deepcopy as deep_copy
9
- from dateutil.relativedelta import \
10
- relativedelta as relative_delta
8
+ from copy import deepcopy
9
+ from dateutil.relativedelta import relativedelta
10
+ from dateutil.parser import parse
11
11
 
12
12
  from .constants import MONTHS_IN_YEAR
13
13
 
@@ -111,7 +111,8 @@ class Timestamp(object):
111
111
 
112
112
  @wraps(func)
113
113
  def wrapper(self, other):
114
- # Subtracting a date-like 'other' returns a datetime.timedelta object.
114
+ # Subtracting a date-like 'other' returns a datetime.timedelta
115
+ # object.
115
116
  if func.__name__.replace('_', '') == 'sub' and \
116
117
  not isinstance(other, (dict, float, int)):
117
118
  return func(self, self._to_datetime(other))
@@ -163,9 +164,9 @@ class Timestamp(object):
163
164
  characters (e.g. 'Friday' or 'Fri') not case-sensitive. Also
164
165
  supports period ends (e.g. 'QE', 'ME').
165
166
  offset : int
166
- Offset value +/- indicating how many additional weeks to shift.
167
- For example, self.next('Mon', offset=+1) would return the Monday
168
- two weeks from self.
167
+ Offset value +/- indicating how many additional weeks to
168
+ shift. For example, self.next('Mon', offset=+1) would
169
+ return the Monday two weeks from self.
169
170
 
170
171
  Returns
171
172
  ------------
@@ -565,7 +566,7 @@ class Timestamp(object):
565
566
 
566
567
  def to_datetime(self):
567
568
  ''' returns a copy of the underlying datetime.datetime object '''
568
- return deep_copy(self.dt)
569
+ return deepcopy(self.dt)
569
570
 
570
571
 
571
572
  def to_pandas(self):
@@ -588,14 +589,9 @@ class Timestamp(object):
588
589
  return self.dt.time()
589
590
 
590
591
 
591
- def to_string(self, fmt):
592
+ def to_string(self, format):
592
593
  ''' strftime alias '''
593
- return self.strftime(fmt)
594
-
595
-
596
- def str(self, *args, **kwargs):
597
- ''' to_string() alias '''
598
- return self.to_string(*args, **kwargs)
594
+ return self.strftime(format)
599
595
 
600
596
 
601
597
  @Decorators.skip_shift
@@ -639,7 +635,7 @@ class Timestamp(object):
639
635
  if not kwargs:
640
636
  raise ValueError("'kwargs' is empty?")
641
637
 
642
- return (relative_delta if relative else
638
+ return (relativedelta if relative else
643
639
  datetime.timedelta)(**kwargs)
644
640
 
645
641
 
@@ -836,7 +832,7 @@ class Timestamp(object):
836
832
 
837
833
 
838
834
  @classmethod
839
- def _to_datetime(cls, arg, **kwargs):
835
+ def _to_datetime(cls, arg, format=None, offset=0):
840
836
  '''
841
837
  Description
842
838
  ------------
@@ -888,8 +884,8 @@ class Timestamp(object):
888
884
  qe_cls = cls._get_quarter_end_cls()
889
885
  parsed = qe_cls.parse_label(arg)
890
886
  if parsed is None: return
891
- year, qtr = parsed
892
- qe = qe_cls(year=year, qtr=qtr, offset=offset)
887
+ year, quarter = parsed
888
+ qe = qe_cls(year=year, quarter=quarter, offset=offset)
893
889
  return qe.dt
894
890
 
895
891
 
@@ -900,14 +896,16 @@ class Timestamp(object):
900
896
  raise NotImplementedError
901
897
 
902
898
  if isinstance(arg, str):
903
- if not kwargs.get('format'):
904
- offset = cls._try_int(kwargs.pop('offset', 0))
905
- for parser in (try_weekday, try_quarter_end):
906
- result = parser(arg, offset)
907
- if result is not None:
908
- return result
909
-
910
- return pd.to_datetime(arg, **kwargs).to_pydatetime()
899
+ if format is not None:
900
+ return datetime.datetime.strptime(arg, format)
901
+
902
+ offset = cls._try_int(offset)
903
+ for func in (try_weekday, try_quarter_end):
904
+ result = func(arg, offset)
905
+ if result is not None:
906
+ return result
907
+
908
+ return parse(arg)
911
909
 
912
910
  # Timestamp instance (or subclass)
913
911
  if isinstance(arg, Timestamp):
@@ -1,3 +1,4 @@
1
+ from oddments import validate_value
1
2
  import re
2
3
 
3
4
 
@@ -48,7 +49,7 @@ def format_elapsed_seconds(seconds, n_digits=2):
48
49
  return ', '.join(parts)
49
50
 
50
51
 
51
- def date_format_to_regex(date_format, encase=False):
52
+ def temporal_format_to_regex(format, encase=False):
52
53
  '''
53
54
  Description
54
55
  ------------
@@ -63,7 +64,7 @@ def date_format_to_regex(date_format, encase=False):
63
64
 
64
65
  Parameters
65
66
  ------------
66
- date_format : str
67
+ format : str
67
68
  String of datetime format codes (e.g. '%Y-%m-%d').
68
69
  encase : bool
69
70
  If True, the output will be encased in parenthesis.
@@ -73,9 +74,11 @@ def date_format_to_regex(date_format, encase=False):
73
74
  pattern : str
74
75
  regex pattern
75
76
  '''
76
- mapping = {}
77
+ validate_value(value=format, attr='format', types=str)
78
+ validate_value(value=encase, attr='encase', types=bool)
77
79
 
78
80
  digit_pattern = lambda x: r'\d{%d}' % x
81
+ mapping = {}
79
82
 
80
83
  for k, v in [('w', 1), ('j', 3), ('Y', 4), ('f', 6)]:
81
84
  mapping['%' + k] = digit_pattern(v)
@@ -96,10 +99,10 @@ def date_format_to_regex(date_format, encase=False):
96
99
  mapping['%' + k] = r'(?:%s)' % '|'.join(v)
97
100
 
98
101
  for k in ['z','Z','c','x','X']:
99
- if '%' + k in date_format:
102
+ if '%' + k in format:
100
103
  raise NotImplementedError
101
104
 
102
- pattern = re.escape(date_format)
105
+ pattern = re.escape(format)
103
106
 
104
107
  for k, v in mapping.items():
105
108
  pattern = pattern.replace(re.escape(k), v)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "clockwork"
3
- version = "0.3.3"
3
+ version = "0.3.5"
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"
@@ -14,7 +14,7 @@ pandas = "*"
14
14
  numpy = "*"
15
15
  schedule = "*"
16
16
  holidays = "*"
17
- oddments = ">=0.3.0"
17
+ oddments = ">=0.3.1"
18
18
 
19
19
  [build-system]
20
20
  requires = ["poetry-core"]
File without changes
File without changes