Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import logging 

2import re 

3import traceback 

4from datetime import datetime, timedelta 

5from typing import Tuple, List, Any, Optional 

6from django.core.management.base import BaseCommand, CommandParser 

7from django.utils.timezone import now 

8from django.conf import settings 

9from jutil.dates import last_month, yesterday, TIME_RANGE_NAMES, TIME_STEP_NAMES, this_month, last_year, last_week, \ 

10 localize_time_range, this_year, this_week, get_time_steps 

11from jutil.email import send_email 

12import getpass 

13from django.utils import translation 

14from jutil.parse import parse_datetime 

15 

16 

17logger = logging.getLogger(__name__) 

18 

19 

20class SafeCommand(BaseCommand): 

21 """ 

22 BaseCommand which catches, logs and emails errors. 

23 Uses list of emails from settings.ADMINS. 

24 Implement do() in derived classes. 

25 """ 

26 def handle(self, *args, **options): 

27 try: 

28 if hasattr(settings, 'LANGUAGE_CODE'): 

29 translation.activate(settings.LANGUAGE_CODE) 

30 return self.do(*args, **options) 

31 except Exception as e: 

32 msg = "ERROR: {} {}".format(str(e), traceback.format_exc()) 

33 logger.error(msg) 

34 if not settings.DEBUG: 

35 send_email(settings.ADMINS, 'Error @ {}'.format(getpass.getuser()), msg) 

36 raise 

37 

38 def do(self, *args, **kwargs): 

39 pass 

40 

41 

42def add_date_range_arguments(parser: CommandParser): 

43 """ 

44 Adds following arguments to the CommandParser: 

45 

46 Ranges: 

47 --begin BEGIN 

48 --end END 

49 --last-year 

50 --last-month 

51 --last-week 

52 --this-year 

53 --this-month 

54 --this-week 

55 --yesterday 

56 --today 

57 --prev-90d 

58 --plus-minus-90d 

59 --next-90d 

60 --prev-60d 

61 --plus-minus-60d 

62 --next-60d 

63 --prev-30d 

64 --plus-minus-30d 

65 --next-30d 

66 --prev-15d 

67 --plus-minus-15d 

68 --next-15d 

69 --prev-7d 

70 --plus-minus-7d 

71 --next-7d 

72 --prev-2d 

73 --plus-minus-2d 

74 --next-2d 

75 --prev-1d 

76 --plus-minus-1d 

77 --next-1d 

78 

79 Steps: 

80 --daily 

81 --weekly 

82 --monthly 

83 

84 :param parser: 

85 :return: 

86 """ 

87 parser.add_argument('--begin', type=str) 

88 parser.add_argument('--end', type=str) 

89 for v in TIME_STEP_NAMES: 

90 parser.add_argument('--' + v.replace('_', '-'), action='store_true') 

91 for v in TIME_RANGE_NAMES: 

92 parser.add_argument('--' + v.replace('_', '-'), action='store_true') 

93 

94 

95def get_date_range_by_name(name: str, today: Optional[datetime] = None, tz: Any = None) -> Tuple[datetime, datetime]: 

96 """ 

97 Returns a timezone-aware date range by symbolic name. 

98 :param name: Name of the date range. See add_date_range_arguments(). 

99 :param today: Optional current datetime. Default is now(). 

100 :param tz: Optional timezone. Default is UTC. 

101 :return: datetime (begin, end) 

102 """ 

103 if today is None: 

104 today = datetime.utcnow() 

105 if name == 'last_week': 

106 return last_week(today, tz) 

107 if name == 'last_month': 

108 return last_month(today, tz) 

109 if name == 'last_year': 

110 return last_year(today, tz) 

111 if name == 'this_week': 

112 return this_week(today, tz) 

113 if name == 'this_month': 

114 return this_month(today, tz) 

115 if name == 'this_year': 

116 return this_year(today, tz) 

117 if name == 'yesterday': 

118 return yesterday(today, tz) 

119 if name == 'today': 

120 begin = today.replace(hour=0, minute=0, second=0, microsecond=0) 

121 end = begin + timedelta(hours=24) 

122 return localize_time_range(begin, end, tz) 

123 m = re.match(r'^plus_minus_(\d+)d$', name) 

124 if m: 

125 days = int(m.group(1)) 

126 return localize_time_range(today - timedelta(days=days), today + timedelta(days=days), tz) 

127 m = re.match(r'^prev_(\d+)d$', name) 

128 if m: 

129 days = int(m.group(1)) 

130 return localize_time_range(today - timedelta(days=days), today, tz) 

131 m = re.match(r'^next_(\d+)d$', name) 

132 if m: 132 ↛ 135line 132 didn't jump to line 135, because the condition on line 132 was never false

133 days = int(m.group(1)) 

134 return localize_time_range(today, today + timedelta(days=days), tz) 

135 raise ValueError('Invalid date range name: {}'.format(name)) 

136 

137 

138def parse_date_range_arguments(options: dict, default_range: str = 'last_month') -> Tuple[datetime, datetime, List[Tuple[datetime, datetime]]]: 

139 """ 

140 Parses date range from input and returns timezone-aware date range and 

141 interval list according to 'step' name argument (optional). 

142 See add_date_range_arguments() 

143 :param options: Parsed arguments passed to the command 

144 :param default_range: Default datetime range to return if no other selected 

145 :return: begin, end, [(begin1,end1), (begin2,end2), ...] 

146 """ 

147 begin, end = get_date_range_by_name(default_range) 

148 for range_name in TIME_RANGE_NAMES: 

149 if options.get(range_name): 149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true

150 begin, end = get_date_range_by_name(range_name) 

151 if options.get('begin'): 151 ↛ 154line 151 didn't jump to line 154, because the condition on line 151 was never false

152 begin = parse_datetime(options['begin']) # type: ignore 

153 end = now() 

154 if options.get('end'): 154 ↛ 157line 154 didn't jump to line 157, because the condition on line 154 was never false

155 end = parse_datetime(options['end']) # type: ignore 

156 

157 step_type = '' 

158 for step_name in TIME_STEP_NAMES: 

159 if options.get(step_name): 159 ↛ 160line 159 didn't jump to line 160, because the condition on line 159 was never true

160 if step_type: 

161 raise ValueError('Cannot use --{} and --{} simultaneously'.format(step_type, step_name)) 

162 step_type = step_name 

163 if step_type: 163 ↛ 164line 163 didn't jump to line 164, because the condition on line 163 was never true

164 steps = get_time_steps(step_type, begin, end) 

165 else: 

166 steps = [(begin, end)] 

167 return begin, end, steps