Coverage for src\report\report.py: 86%

114 statements  

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

1"""Command-line interface for report of Monaco race. 

2 

3Class: 

4 OrderedNamespace 

5 Cli 

6""" 

7import os 

8import sys 

9import argparse 

10from dataclasses import dataclass, field 

11from typing import Any 

12 

13from tabulate import tabulate 

14 

15import report.constant as c 

16from report.race import Race 

17 

18 

19class OrderedNamespace(argparse.Namespace): 

20 """Namespace with ordered arguments. 

21 

22 Adds dictionary which saves order of input arguments in cli. 

23 """ 

24 

25 def __init__(self, **kwargs): 

26 """Initialize with new attribute 

27 

28 Args: 

29 kwargs: arguments in parser 

30 """ 

31 # _order will save input arguments in given order 

32 self.__dict__["_order"] = [None] 

33 super().__init__(**kwargs) 

34 

35 def __setattr__(self, attr: str, value: Any): 

36 """Set attributes. 

37 

38 _order will contain only attribute names, that was 

39 provided in cli in preserved order. 

40 

41 Args: 

42 attr: Attributes in Namespace. 

43 value: Values of attributes in Namespace. 

44 """ 

45 super().__setattr__(attr, value) 

46 if attr in self._order: 

47 self.__dict__["_order"].clear() 

48 self.__dict__["_order"].append(attr) 

49 

50 def ordered(self) -> list[tuple]: 

51 """Generate values for attributes in _order. 

52 

53 Returns: 

54 Return _order pairs of attributes and values. 

55 """ 

56 return [(attr, getattr(self, attr)) for attr in self._order 

57 if attr != "files"] 

58 

59 def only_file_path(self): 

60 """Checks argument quantity. 

61 

62 Checks if any argument was given beside files path argument. 

63 

64 Raises: 

65 UserWarning: If only files path was given as argument. 

66 """ 

67 if len(self.__dict__["_order"]) == 1: 

68 raise UserWarning("Application should use at least one argument (except 'files' argument!)") 

69 

70 

71@dataclass 

72class Cli: 

73 _print_func: dict = field(init=False, default_factory=dict) 

74 args: argparse.Namespace = field(init=False) 

75 race: Race = field(init=False) 

76 

77 def __post_init__(self): 

78 """Initialize print_function variable""" 

79 # Save all print functions in dictionary in order to make execution simpler. 

80 self._print_func = {"report": self.print_report, 

81 "time": self.print_time, 

82 "date": self.print_date_of_the_race, 

83 "racers_number": self.print_number_of_racers, 

84 "driver": self.print_driver, 

85 "racers": self.print_racers, 

86 "teams": self.print_teams, 

87 "team_racers": self.print_racers_in_team} 

88 

89 @staticmethod 

90 def get_path_to_files(path: str) -> dict[str: str]: 

91 """Get path to the log files. 

92 

93 Args: 

94 path: Path of the folder where log files are located. 

95 

96 Returns: 

97 Dictionary with file name as key and path to the file as value. 

98 

99 Raises: 

100 UserWarning: if not all files where found in the folder. 

101 NotADirectoryError: if path argument is not a dictionary. 

102 FileNotFoundError: if path does not exist. 

103 """ 

104 # Get path of the log files from directory if file name is in lookup list 

105 file_path = {os.path.splitext(f)[0]: os.path.join(path, f) 

106 for f in os.listdir(path) 

107 if f.lower() in c.REPORT_FILES} 

108 # Check if dictionary has all necessary files. 

109 if len(file_path) != len(c.REPORT_FILES): 

110 raise UserWarning("File missing. Make sure that folder contains this files:" 

111 f" {c.REPORT_FILES}") 

112 return file_path 

113 

114 def print_date_of_the_race(self): 

115 """Print date of the race.""" 

116 print("\nDate of the race:") 

117 print(f"\t{self.race.get_date_of_race()}") 

118 

119 def print_time(self): 

120 """Print start time of the 1st qualification.""" 

121 print("\n1st qualification round started at:") 

122 print(f"\t{self.race.get_start_time_of_q1()}") 

123 

124 def print_number_of_racers(self): 

125 """Print number of racers in the race.""" 

126 print("\nNumber of the racers in the race:") 

127 print(f"\t{self.race.get_number_of_racers()}") 

128 

129 def print_report(self): 

130 """Print race report. 

131 

132 Prints race report in nice table look. 

133 """ 

134 # Build report of the race. 

135 race_report = self.race.get_report() 

136 racers = [] 

137 print("\nReport of the race:") 

138 # Loop through report sorted by lap time. 

139 for num, racer in enumerate(race_report): 

140 # Convert time lap to appropriate string format. 

141 racer[2] = str(racer[2])[3: -3] 

142 # Insert racer's place. 

143 racer.insert(0, str(num + 1)) 

144 racers.append(racer) 

145 # Separate first 15 places. 

146 if num + 1 == 15: 

147 # Calculate separation line length for each column. 

148 team_sep = "-" * len(max(self.race.get_teams(), key=len)) 

149 racer_sep = "-" * len(max(self.race.get_names_of_racers(), key=len)) 

150 time_sep = "-" * len(racer[3]) 

151 place_sep = "--" 

152 # Add separation line. 

153 racers.append([place_sep, racer_sep, team_sep, time_sep]) 

154 print(tabulate(racers, 

155 headers=["", "Full Name", "Team", "Q1 Time"], 

156 tablefmt=c.TABLE_FORMAT)) 

157 

158 def print_driver(self, name: str): 

159 """Print driver information. 

160 

161 Args: 

162 name: Driver full name. 

163 

164 Raises: 

165 UserWarning: if no racer found with given name. 

166 """ 

167 try: 

168 driver = self.race.get_racer(name) 

169 print(f"\nInformation about {driver[1]}:") 

170 print(tabulate([driver], 

171 headers=["Abbr", "Full Name", "Team", "Q1 Time"], 

172 tablefmt=c.TABLE_FORMAT)) 

173 except UserWarning as err: 

174 print(err) 

175 

176 def print_racers(self, order: str): 

177 """Print list of racers in the race. 

178 

179 Args: 

180 order: defines order of the result list. 

181 """ 

182 racers = [[racer] for racer in 

183 self.race.get_names_of_racers(reverse=c.ORDER[order])] 

184 print(tabulate(racers, 

185 headers=["List of racers:"], 

186 tablefmt=c.TABLE_FORMAT)) 

187 

188 def print_teams(self, order: str): 

189 """Print list of teams in the race. 

190 

191 Args: 

192 order: Flag for racers order. 

193 """ 

194 teams = [[team] for team in self.race.get_teams(reverse=c.ORDER[order])] 

195 print(tabulate(teams, 

196 headers=["List of teams:"], 

197 tablefmt=c.TABLE_FORMAT)) 

198 

199 def print_racers_in_team(self, team: str): 

200 """Print racers in specific team. 

201 

202 Args: 

203 team: Name of the team. 

204 """ 

205 try: 

206 racers = self.race.get_racers_in_team(team) 

207 print(f"\nRacers from {team.upper()} team:") 

208 print(tabulate(racers, 

209 headers=["Abbr", "Full Name", "Team", "Q1 Time"], 

210 tablefmt=c.TABLE_FORMAT, )) 

211 except UserWarning as err: 

212 print(err) 

213 

214 def create_race(self, file_path: dict[str: str]): 

215 """Create Race instance 

216 

217 Args: 

218 file_path: path to the files. 

219 """ 

220 self.race = Race(log_files=file_path) 

221 

222 def cli(self): 

223 """Implement command-line interface 

224 

225 Uses argparse to get input from command-line 

226 and print according result. 

227 """ 

228 parser = argparse.ArgumentParser( 

229 prog="Report of Monaco 2018 racing.", 

230 description="Get information about the race from the log files." 

231 ) 

232 parser.add_argument( 

233 "-f", "--files", required=True, 

234 help="directory path where the race log files are located (required argument)" 

235 ) 

236 parser.add_argument( 

237 "-r", "--report", action="store_true", 

238 help="report of the 1st qualification results" 

239 ) 

240 parser.add_argument( 

241 "-d", "--date", action="store_true", 

242 help="date of the 1st qualification" 

243 ) 

244 parser.add_argument( 

245 "-t", "--time", action="store_true", 

246 help="start time of the 1st qualifications" 

247 ) 

248 parser.add_argument( 

249 "--racers-number", action="store_true", 

250 help="number of the racers in the race" 

251 ) 

252 parser.add_argument( 

253 "--driver", 

254 help="information about specific driver" 

255 ) 

256 parser.add_argument( 

257 "--racers", 

258 const=list(c.ORDER.keys())[0], 

259 nargs="?", 

260 choices=c.ORDER.keys(), 

261 help="""list of all racers in the race in asc or" 

262 desc order (default is asc)""" 

263 ) 

264 parser.add_argument( 

265 "--teams", 

266 const=list(c.ORDER.keys())[0], 

267 nargs="?", 

268 choices=c.ORDER.keys(), 

269 help="""list of all teams in the race in asc " 

270 or desc order (default is asc)""" 

271 ) 

272 parser.add_argument( 

273 "--team-racers", 

274 help="list of all racers from specific team" 

275 ) 

276 

277 self.args = parser.parse_args(namespace=OrderedNamespace()) 

278 

279 try: 

280 # Check if only "files" argument was passed 

281 self.args.only_file_path() 

282 except UserWarning as err: 

283 print(err) 

284 parser.print_usage() 

285 sys.exit() 

286 

287 try: 

288 # Get log files from file directory 

289 file_path = self.get_path_to_files(self.args.files) 

290 except (FileNotFoundError, NotADirectoryError, UserWarning) as err: 

291 sys.exit(err) 

292 

293 try: 

294 # Create race instance. 

295 self.create_race(file_path=file_path) 

296 except (TypeError, FileNotFoundError, UserWarning) as err: 

297 sys.exit(err) 

298 

299 # Go through passed arguments and execute according print functions. 

300 for arg, value in self.args.ordered(): 

301 if arg in ["driver", "racers", "teams", "team_racers"]: 

302 self._print_func[arg](value) 

303 else: 

304 self._print_func[arg]() 

305 

306 

307if __name__ == "__main__": 

308 cli = Cli() 

309 cli.cli()