Coverage for jutil/command.py: 79%

# Coverage for jutil/command.py : 79%

## 89 statements

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

1 import logging

2 import re

3 import traceback

4 from datetime import datetime, timedelta

5 from typing import Tuple, List, Any, Optional

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

7 from django.utils.timezone import now

8 from django.conf import settings

9 from jutil.dates import (

10 last_month,

11 yesterday,

12 TIME_RANGE_NAMES,

13 TIME_STEP_NAMES,

14 this_month,

15 last_year,

16 last_week,

17 localize_time_range,

18 this_year,

19 this_week,

20 get_time_steps,

21)

22 from jutil.email import send_email

23 import getpass

24 from django.utils import translation

25 from jutil.parse import parse_datetime

26

27

28 logger = logging.getLogger(__name__)

29

30

31 class SafeCommand(BaseCommand):

32 """

33 BaseCommand which catches, logs and emails errors.

34 Uses list of emails from settings.ADMINS.

35 Implement do() in derived classes.

36 """

37

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

39 try:

40 if hasattr(settings, "LANGUAGE_CODE"):

41 translation.activate(settings.LANGUAGE_CODE)

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

43 except Exception as e:

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

45 logger.error(msg)

46 if not settings.DEBUG:

47 send_email(settings.ADMINS, "Error @ {}".format(getpass.getuser()), msg)

48 raise

49

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

51 pass

52

53

54 def add_date_range_arguments(parser: CommandParser):

55 """

56 Adds following arguments to the CommandParser:

57

58 Ranges:

59 --begin BEGIN

60 --end END

61 --last-year

62 --last-month

63 --last-week

64 --this-year

65 --this-month

66 --this-week

67 --yesterday

68 --today

69 --prev-90d

70 --plus-minus-90d

71 --next-90d

72 --prev-60d

73 --plus-minus-60d

74 --next-60d

75 --prev-30d

76 --plus-minus-30d

77 --next-30d

78 --prev-15d

79 --plus-minus-15d

80 --next-15d

81 --prev-7d

82 --plus-minus-7d

83 --next-7d

84 --prev-2d

85 --plus-minus-2d

86 --next-2d

87 --prev-1d

88 --plus-minus-1d

89 --next-1d

90

91 Steps:

92 --daily

93 --weekly

94 --monthly

95

96 :param parser:

97 :return:

98 """

99 parser.add_argument("--begin", type=str)

100 parser.add_argument("--end", type=str)

101 for v in TIME_STEP_NAMES:

102 parser.add_argument("--" + v.replace("_", "-"), action="store_true")

103 for v in TIME_RANGE_NAMES:

104 parser.add_argument("--" + v.replace("_", "-"), action="store_true")

105

106

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

108 """

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

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

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

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

113 :return: datetime (begin, end)

114 """

115 if today is None:

116 today = datetime.utcnow()

117 if name == "last_week":

118 return last_week(today, tz)

119 if name == "last_month":

120 return last_month(today, tz)

121 if name == "last_year":

122 return last_year(today, tz)

123 if name == "this_week":

124 return this_week(today, tz)

125 if name == "this_month":

126 return this_month(today, tz)

127 if name == "this_year":

128 return this_year(today, tz)

129 if name == "yesterday":

130 return yesterday(today, tz)

131 if name == "today":

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

133 end = begin + timedelta(hours=24)

134 return localize_time_range(begin, end, tz)

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

136 if m:

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

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

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

140 if m:

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

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

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

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

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

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

147 raise ValueError("Invalid date range name: {}".format(name))

148

149

150 def parse_date_range_arguments(

151 options: dict, default_range: str = "last_month"

152) -> Tuple[datetime, datetime, List[Tuple[datetime, datetime]]]:

153 """

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

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

156 See add_date_range_arguments()

157 :param options: Parsed arguments passed to the command

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

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

160 """

161 begin, end = get_date_range_by_name(default_range)

162 for range_name in TIME_RANGE_NAMES:

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

164 begin, end = get_date_range_by_name(range_name)

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

166 begin = parse_datetime(options["begin"]) # type: ignore

167 end = now()

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

169 end = parse_datetime(options["end"]) # type: ignore

170

171 step_type = ""

172 for step_name in TIME_STEP_NAMES:

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

174 if step_type:

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

176 step_type = step_name

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

178 steps = get_time_steps(step_type, begin, end)

179 else:

180 steps = [(begin, end)]

181 return begin, end, steps