Coverage for src\report\race.py: 96%

104 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-18 14:42 +0200

1"""Use for creating Race class 

2 

3Class: 

4 Race 

5""" 

6import os 

7from dataclasses import dataclass, field 

8from datetime import datetime, timedelta 

9 

10import report.constant as c 

11 

12 

13@dataclass() 

14class Race: 

15 """Class represents race. 

16 

17 "log_files" argument must be a dictionary with three key-value pairs: 

18 {"abbreviations": "path/to/abbreviations.txt", 

19 "start": "path/to/start.log", 

20 "end": "path/to/end.log"} 

21 

22 Args: 

23 log_files: Log file paths. 

24 racers: dictionary of racers participated in the race. 

25 

26 Raises: 

27 UserError: 

28 if "log_files" argument contains invalid data. 

29 TypeError: 

30 if "log_files" argument contains unacceptable types. 

31 FileNotFoundError: 

32 if file in path from "log_files" can't be found. 

33 PermissionError: 

34 if file in path from "log_files" can't be read. 

35 """ 

36 log_files: dict[str: str] 

37 racers: dict[str: dict[str: str]] = field(init=False, default_factory=dict) 

38 

39 def __post_init__(self): 

40 """Get value to attribute "racers" from "log_files" argument.""" 

41 # Check "log_files" argument for valid structure and content. 

42 self.__validate_log_files_argument() 

43 # Get information about racers. 

44 self.__data_from_abbreviation(self.log_files["abbreviations"]) 

45 # Get start and finish time of racer's best lap. 

46 for key, file_path in self.log_files.items(): 

47 if key in ["start", "end"]: 

48 self.__data_from_log_files(key, file_path) 

49 

50 @staticmethod 

51 def __validate_log_files_len(log_file_len: int): 

52 """Validate length of "log_files" argument 

53 

54 Args: 

55 log_file_len: length of log_files. 

56 """ 

57 if log_file_len != 3: 

58 raise UserWarning("log_files argument should contain three key value pairs. " 

59 f"{log_file_len} was provided.") 

60 

61 @staticmethod 

62 def __validate_log_files_keys(key: str): 

63 """Check if "log_files" argument have all necessary keys. 

64 

65 Args: 

66 key: key in "log_files" argument. 

67 """ 

68 if key not in ["abbreviations", "start", "end"]: 

69 raise UserWarning(f"Invalid key in log_files argument: {key}") 

70 

71 @staticmethod 

72 def __validate_log_files_values(file_path: str): 

73 """Check if file name in "log_files" argument are correct. 

74 

75 Args: 

76 file_path: path of the log file. 

77 """ 

78 try: 

79 if os.path.basename(file_path) not in c.REPORT_FILES: 

80 raise UserWarning(f"Invalid value in log_files argument {file_path}") 

81 except (TypeError, UserWarning): 

82 raise 

83 

84 @staticmethod 

85 def __validate_log_files_is_empty(file_path): 

86 """Check if file in file_path from "log_files" argument is mot empty. 

87 

88 Args: 

89 file_path: path of the log file. 

90 """ 

91 if os.stat(file_path).st_size == 0: 

92 raise UserWarning(f"File is empty: {os.path.basename(file_path)}") 

93 

94 def __validate_log_files_argument(self): 

95 """Validate "log_files" argument. 

96 

97 "log_files" argument must be a dictionary with three key-value pairs: 

98 {"abbreviations": "path/to/abbreviations.txt", 

99 "start": "path/to/start.log", 

100 "end": "path/to/end.log"} 

101 

102 Raises: 

103 UserWarning: if invalid key value pairs. 

104 TypeError: if file_path string can't be used by [os.path.basename]. 

105 """ 

106 try: 

107 # Check length of log_files argument. 

108 self.__validate_log_files_len(len(self.log_files)) 

109 # Check if keys and values are acceptable. 

110 for key, file_path in self.log_files.items(): 

111 self.__validate_log_files_keys(key) 

112 self.__validate_log_files_values(file_path) 

113 self.__validate_log_files_is_empty(file_path) 

114 except (TypeError, UserWarning): 

115 raise 

116 

117 def __data_from_log_files(self, key, file_path): 

118 """Get data from log files. 

119 

120 Read files and get the start and finish time of racers best lap. 

121 

122 Args: 

123 key: key in log_files argument. 

124 file_path: file_path string in log_files argument 

125 

126 Raises: 

127 FileNotFoundError: if file_path is not found. 

128 PermissionError: if program don't have permission to read file_path. 

129 """ 

130 try: 

131 with open(file_path, encoding="utf8") as file: 

132 for line in file: 

133 line = line.strip() 

134 if line: 

135 # First three chars in the line is abbreviation - key, 

136 # rest is 1st qualification start time of the lap 

137 self.racers[line[:3]]["q1_" + key] = datetime.strptime( 

138 line[3:].strip(), "%Y-%m-%d_%H:%M:%S.%f") 

139 except (FileNotFoundError, PermissionError): 

140 raise 

141 

142 def __data_from_abbreviation(self, file_path): 

143 """Get data from abbreviations file. 

144 

145 Read file and get abbreviation, full name and the team of the racer. 

146 

147 Args: 

148 file_path: file_path string in log_files argument 

149 

150 Raises: 

151 FileNotFoundError: if file_path is not found. 

152 PermissionError: if program don't have permission to read file_path. 

153 """ 

154 try: 

155 with open(file_path, encoding="utf8") as file: 

156 for line in file: 

157 line = line.strip() 

158 if line: 

159 # Information about racer is split with underscore: 

160 # "abbreviation_name of the racer_racer team". 

161 racer = line.split("_") 

162 # Abbreviation is the key in dictionary of racers. 

163 self.racers[racer[0]] = {"name": racer[1], "team": racer[2]} 

164 except (FileNotFoundError, PermissionError): 

165 raise 

166 

167 def get_number_of_racers(self) -> int: 

168 """Get number of the racers in the race. 

169 

170 Returns: 

171 Number of the racers in the race. 

172 """ 

173 number_of_racers = len(self.racers.keys()) 

174 return number_of_racers 

175 

176 def get_date_of_race(self) -> datetime.date: 

177 """Get date in which race took place 

178 

179 Returns: 

180 Date of the race. 

181 """ 

182 race_date = self.racers[list(self.racers.keys())[0]]["q1_start"].date() 

183 return race_date 

184 

185 def get_start_time_of_q1(self) -> datetime.time: 

186 """Get start time of the 1st qualification. 

187 

188 Returns: 

189 1st qualifications start time. 

190 """ 

191 q1_start_time = None 

192 # Loop through all racers q1_start time and find the earliest one. 

193 for racer in self.racers.values(): 

194 racer_start_time = racer["q1_start"].time() 

195 if q1_start_time: 

196 # Check if racer_stat_time is earlier than q1_start_time. 

197 if q1_start_time > racer_start_time: 

198 q1_start_time = racer_start_time 

199 else: 

200 # If q1 start time is None assign with first racers start time. 

201 q1_start_time = racer_start_time 

202 return q1_start_time 

203 

204 def get_names_of_racers(self, reverse=False) -> list[str]: 

205 """Get all racer names in the race. 

206 

207 Collect all racer names who are participating in the race 

208 by asc or desc order. 

209 

210 Args: 

211 reverse: Defines order of racer names in the list. 

212 If True than order of the racer names is reverse. 

213 

214 Returns: 

215 List of racer names in asc or desc order. 

216 """ 

217 racers_names = [racer["name"] for racer in self.racers.values()] 

218 racers_names.sort(reverse=reverse) 

219 return racers_names 

220 

221 def get_teams(self, reverse=False) -> list[str]: 

222 """Get all team names in race. 

223 

224 Collect all team names which are participating in the race 

225 by asc or desc order. 

226 

227 Args: 

228 reverse: Defines order of team names in the list. 

229 If True than order of the team names is reverse. 

230 

231 Returns: 

232 List of team names in asc or desc order. 

233 """ 

234 # Use set to filter unique teams 

235 teams_in_race = {racer["team"] for racer in self.racers.values()} 

236 teams_in_race = sorted(teams_in_race, reverse=reverse) 

237 return teams_in_race 

238 

239 def get_report(self) -> list[list[str, str, timedelta]]: 

240 """Build result of the 1st qualification. 

241 

242 Returns: 

243 List of Racers name, team, lap time. 

244 """ 

245 race_report = [ 

246 [racer["name"], 

247 racer["team"], 

248 (racer["q1_end"] - racer["q1_start"])] 

249 for racer in self.racers.values()] 

250 # Sort report by lap time. 

251 race_report = sorted(race_report, key=lambda x: x[2]) 

252 return race_report 

253 

254 def get_racer(self, name: str) -> list: 

255 """Get racer data. 

256 

257 Args: 

258 name: Name of the racer. 

259 

260 Returns: 

261 Racers data. 

262 

263 Raises: 

264 UserWarning when name of the racers is not in dictionary of racers. 

265 """ 

266 for key, racer in self.racers.items(): 

267 if racer["name"] == name: 

268 racer_data = [key, racer["name"], racer["team"], 

269 str(racer["q1_end"] - racer["q1_start"])[3: -3]] 

270 return racer_data 

271 raise UserWarning(f"Can't find racer with name: {name}") 

272 

273 def get_racers_in_team(self, team: str, order=False) -> list[list]: 

274 """Get racers from the team. 

275 

276 Args: 

277 team: Name of the team. 

278 order: Defines order of racer names in the list. 

279 If True than order of the racer names is reverse. 

280 

281 Returns: 

282 List of racers data. 

283 

284 Raises: 

285 UserWarning when team is not in dictionary of racers. 

286 """ 

287 racers_in_team = [] 

288 for key, racer in self.racers.items(): 

289 if racer["team"] == team.upper(): 

290 racer_data = [key, racer["name"], racer["team"], 

291 str(racer["q1_end"] - racer["q1_start"])[3: -3]] 

292 racers_in_team.append(racer_data) 

293 racers_in_team = sorted(racers_in_team, key=lambda x: x[0], reverse=order) 

294 if racers_in_team: 

295 return racers_in_team 

296 raise UserWarning(f"Can't find team with name: {team}")