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
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-18 14:42 +0200
1"""Use for creating Race class
3Class:
4 Race
5"""
6import os
7from dataclasses import dataclass, field
8from datetime import datetime, timedelta
10import report.constant as c
13@dataclass()
14class Race:
15 """Class represents race.
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"}
22 Args:
23 log_files: Log file paths.
24 racers: dictionary of racers participated in the race.
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)
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)
50 @staticmethod
51 def __validate_log_files_len(log_file_len: int):
52 """Validate length of "log_files" argument
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.")
61 @staticmethod
62 def __validate_log_files_keys(key: str):
63 """Check if "log_files" argument have all necessary keys.
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}")
71 @staticmethod
72 def __validate_log_files_values(file_path: str):
73 """Check if file name in "log_files" argument are correct.
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
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.
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)}")
94 def __validate_log_files_argument(self):
95 """Validate "log_files" argument.
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"}
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
117 def __data_from_log_files(self, key, file_path):
118 """Get data from log files.
120 Read files and get the start and finish time of racers best lap.
122 Args:
123 key: key in log_files argument.
124 file_path: file_path string in log_files argument
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
142 def __data_from_abbreviation(self, file_path):
143 """Get data from abbreviations file.
145 Read file and get abbreviation, full name and the team of the racer.
147 Args:
148 file_path: file_path string in log_files argument
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
167 def get_number_of_racers(self) -> int:
168 """Get number of the racers in the race.
170 Returns:
171 Number of the racers in the race.
172 """
173 number_of_racers = len(self.racers.keys())
174 return number_of_racers
176 def get_date_of_race(self) -> datetime.date:
177 """Get date in which race took place
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
185 def get_start_time_of_q1(self) -> datetime.time:
186 """Get start time of the 1st qualification.
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
204 def get_names_of_racers(self, reverse=False) -> list[str]:
205 """Get all racer names in the race.
207 Collect all racer names who are participating in the race
208 by asc or desc order.
210 Args:
211 reverse: Defines order of racer names in the list.
212 If True than order of the racer names is reverse.
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
221 def get_teams(self, reverse=False) -> list[str]:
222 """Get all team names in race.
224 Collect all team names which are participating in the race
225 by asc or desc order.
227 Args:
228 reverse: Defines order of team names in the list.
229 If True than order of the team names is reverse.
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
239 def get_report(self) -> list[list[str, str, timedelta]]:
240 """Build result of the 1st qualification.
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
254 def get_racer(self, name: str) -> list:
255 """Get racer data.
257 Args:
258 name: Name of the racer.
260 Returns:
261 Racers data.
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}")
273 def get_racers_in_team(self, team: str, order=False) -> list[list]:
274 """Get racers from the team.
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.
281 Returns:
282 List of racers data.
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}")