oleksiisaiun_lab6_monaco 1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ from .core import race_report
2
+ __all__ = ["main_run"]
3
+
4
+
5
+
@@ -0,0 +1,16 @@
1
+ import re
2
+
3
+
4
+ ABBR_ROW_PATTERN = re.compile(
5
+ r"^(?P<abbr>[A-Z]{3})_(?P<driver>[A-Za-z .'-]+)_(?P<team>[A-Z0-9 &'()-]+)$"
6
+ )
7
+
8
+
9
+ START_STOP_ROW_PATTERN = re.compile(
10
+ r'^(?P<abbr>[A-Z]{3})(?P<time_event>\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}\.\d{3})$'
11
+ )
12
+
13
+ ERR_MSG__LAP_TIME_ZERO_OR_NEGATIVE="LAP_TIME_CAN_NOT_BE_ZERO_OR_NEGATIVE"
14
+ ERR_MSG__EMPTY_START_OR_STOP_TIME="EMPTY_START_OR_STOP_TIME"
15
+ ERR_MSG__INVALID_FORMAT_OF_TIME_EVENT_ROW ='INVALID_FORMAT_OF_TIME_EVENT_ROW'
16
+ ERR_PREFIX='INVALID_ABBREVIATION_ROW_'
@@ -0,0 +1,19 @@
1
+ DRR_Daniel Ricciardo_RED BULL RACING TAG HEUER
2
+ SVF_Sebastian Vettel_FERRARI
3
+ LHM_Lewis Hamilton_MERCEDES
4
+ KRF_Kimi Räikkönen_FERRARI
5
+ VBM_Valtteri Bottas_MERCEDES
6
+ EOF_Esteban Ocon_FORCE INDIA MERCEDES
7
+ FAM_Fernando Alonso_MCLAREN RENAULT
8
+ CSR_Carlos Sainz_RENAULT
9
+ SPF_Sergio Perez_FORCE INDIA MERCEDES
10
+ PGS_Pierre Gasly_SCUDERIA TORO ROSSO HONDA
11
+ NHR_Nico Hulkenberg_RENAULT
12
+ SVM_Stoffel Vandoorne_MCLAREN RENAULT
13
+ SSW_Sergey Sirotkin_WILLIAMS MERCEDES
14
+ CLS_Charles Leclerc_SAUBER FERRARI
15
+ RGH_Romain Grosjean_HAAS FERRARI
16
+ BHS_Brendon Hartley_SCUDERIA TORO ROSSO HONDA
17
+ MES_Marcus Ericsson_SAUBER FERRARI
18
+ LSW_Lance Stroll_WILLIAMS MERCEDES
19
+ KMH_Kevin Magnussen_HAAS FERRARI
@@ -0,0 +1,19 @@
1
+ MES2018-05-24_12:05:58.778
2
+ RGH2018-05-24_12:06:27.441
3
+ SPF2018-05-24_12:13:13.883
4
+ LSW2018-05-24_12:07:26.834
5
+ DRR2018-05-24_12:11:24.067
6
+ NHR2018-05-24_12:04:02.979
7
+ CSR2018-05-24_12:04:28.095
8
+ KMH2018-05-24_12:04:04.396
9
+ BHS2018-05-24_12:16:05.164
10
+ SVM2018-05-24_12:19:50.198
11
+ KRF2018-05-24_12:04:13.889
12
+ VBM2018-05-24_12:01:12.434
13
+ SVF2018-05-24_12:04:03.332
14
+ EOF2018-05-24_12:12:11.838
15
+ PGS2018-05-24_12:08:36.586
16
+ SSW2018-05-24_12:11:24.354
17
+ FAM2018-05-24_12:14:17.169
18
+ CLS2018-05-24_12:10:54.750
19
+ LHM2018-05-24_12:11:32.585
@@ -0,0 +1,20 @@
1
+ SVF2018-05-25_12:59:58.917
2
+ NHR2018-05-24_12:02:49.914
3
+ FAM2018-05-24_12:13:04.512
4
+ KRF2018-05-24_12:03:01.250
5
+ SVM2018-05-24_12:18:37.735
6
+ MES2018-05-24_12:04:45.513
7
+ LSW2018-05-24_12:06:13.511
8
+ BHS2018-05-24_12:14:51.985
9
+ EOF2018-05-24_12:17:58.810
10
+ RGH2018-05-24_12:05:14.511
11
+ SSW2018-05-24_12:16:11.648
12
+ KMH2018-05-24_12:02:51.003
13
+ PGS2018-05-24_12:07:23.645
14
+ CSR2018-05-24_12:03:15.145
15
+ SPF2018-05-24_12:12:01.035
16
+ DRR2018-05-24_12:14:12.054
17
+ LHM2018-05-24_12:18:20.125
18
+ CLS2018-05-24_12:09:41.921
19
+ VBM2018-05-24_12:00:00.000
20
+
@@ -0,0 +1,183 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from datetime import datetime
4
+ import utilities as util
5
+ from constants import *
6
+
7
+
8
+ @dataclass
9
+ class RecordData:
10
+ abbr: str = None
11
+ driver: str = None
12
+ team: str = None
13
+ _start: datetime = None
14
+ _stop: datetime = None
15
+ _lap_time_seconds: int = None
16
+ error: list[dict[str, str]] = None
17
+
18
+ @property
19
+ def start(self):
20
+ return self._start
21
+
22
+ @start.setter
23
+ def start(self, value):
24
+ self._start = value
25
+
26
+ @property
27
+ def stop(self):
28
+ return self._stop
29
+
30
+ @stop.setter
31
+ def stop(self, value):
32
+ self._stop = value
33
+
34
+ @property
35
+ def lap_time_seconds(self):
36
+ return self._lap_time_seconds
37
+
38
+ @lap_time_seconds.setter
39
+ def lap_time_seconds(self, value):
40
+ if float(value) <= 0:
41
+ error_value = {self.abbr: ERR_MSG__LAP_TIME_ZERO_OR_NEGATIVE}
42
+ if self.error is None:
43
+ self.error = []
44
+ self.error.append(error_value)
45
+ else:
46
+ self._lap_time_seconds = value
47
+
48
+ def __str__(self):
49
+ return (
50
+ f"RecordData("
51
+ f"abbr='{self.abbr}', "
52
+ f"driver='{self.driver}', "
53
+ f"team='{self.team}', "
54
+ f"start='{self.start}', "
55
+ f"stop='{self.stop}', "
56
+ f"lap_time_seconds={self.lap_time_seconds}, "
57
+ f"error={self.error}"
58
+ f")"
59
+ )
60
+
61
+
62
+ def _create_driver_record(row) -> RecordData:
63
+ record_driver_out = util.validate_abbreviation_row(row)
64
+ if record_driver_out is None:
65
+ error_val = {row: ERR_PREFIX}
66
+ record_driver_out = RecordData(error=[error_val])
67
+ return record_driver_out
68
+
69
+
70
+ def _read_file_abbreviation(filepath) -> list[dict[str, RecordData], dict[str, RecordData]]:
71
+ dict_records_good = dict()
72
+ dict_records_bad = dict()
73
+ with open(filepath, 'r', encoding='utf-8') as file:
74
+ for line_numerator, line in enumerate(file, start=1):
75
+ row = line.strip()
76
+
77
+ if not row:
78
+ continue # skip empty lines
79
+ driver_record = _create_driver_record(row)
80
+
81
+ if not driver_record.error:
82
+ dict_records_good[driver_record.abbr] = driver_record
83
+ else:
84
+ key_invalid_row = str(ERR_PREFIX + str(line_numerator))
85
+ dict_records_bad[key_invalid_row] = driver_record
86
+ return [dict_records_good, dict_records_bad]
87
+
88
+
89
+ def _read_file_start_stop(filepath: str) -> dict[str, datetime]:
90
+ dict_out = dict()
91
+ with open(filepath, 'r', encoding='utf-8') as file:
92
+ for line in file:
93
+ line = line.strip()
94
+ if not line:
95
+ continue # Skip empty lines
96
+
97
+ time_event_record = util.validate_start_stop_row(line)
98
+
99
+ if time_event_record is not None:
100
+ abbr = time_event_record[0]
101
+ time_event = time_event_record[1]
102
+ dict_out[abbr] = time_event
103
+ return dict_out
104
+
105
+
106
+ def build_report(
107
+ folder: str,
108
+ file_abbr: str,
109
+ file_start: str,
110
+ file_stop: str,
111
+ ) -> tuple[list[RecordData], list[RecordData]]:
112
+ filepath_abbr = os.path.join(os.path.dirname(__file__), folder, file_abbr)
113
+ filepath_start = os.path.join(os.path.dirname(__file__), folder, file_start)
114
+ filepath_stop = os.path.join(os.path.dirname(__file__), folder, file_stop)
115
+ list_records_good = list()
116
+ list_records_bad = list()
117
+
118
+ if (util.validate_if_file_exists(filepath_abbr)) and (util.validate_if_file_exists(filepath_start)) and (
119
+ util.validate_if_file_exists(filepath_stop)):
120
+ [dict_records_good, dict_records_bad] = _read_file_abbreviation(filepath_abbr)
121
+
122
+ dict_start = _read_file_start_stop(filepath=filepath_start)
123
+ dict_stop = _read_file_start_stop(filepath=filepath_stop)
124
+
125
+ for key_abbr, val_abbr in dict_records_good.items():
126
+ driver_record_current = dict_records_good[key_abbr]
127
+ key_start_time = dict_start[key_abbr]
128
+ key_stop_time = dict_stop[key_abbr]
129
+
130
+ if key_start_time and key_stop_time:
131
+ driver_record_current.start = key_start_time
132
+ driver_record_current.stop = key_stop_time
133
+ lap_time_seconds = (key_stop_time - key_start_time).total_seconds()
134
+ driver_record_current.lap_time_seconds = lap_time_seconds
135
+ list_records_good.append(driver_record_current)
136
+ else:
137
+ list_records_bad.append()
138
+
139
+ for k in dict_records_bad:
140
+ list_records_bad.append(dict_records_bad[k])
141
+
142
+ # error values can be assigned in the property [lap_time_seconds]. I check if there are any Records with errors:
143
+ for key_abbr, val_abbr in dict_records_good.items():
144
+ driver_record_current = dict_records_good[key_abbr]
145
+ if driver_record_current.error is not None:
146
+ list_records_good.remove(driver_record_current)
147
+ list_records_bad.append(driver_record_current)
148
+ output = [list_records_good, list_records_bad]
149
+
150
+ return output
151
+
152
+
153
+ def print_report(records_good: list[RecordData], records_bad: list[RecordData], sort_by_lap_asc: bool = True):
154
+ print(f"---------RACE REPORT OF GOOD RECORDS--------------")
155
+ if sort_by_lap_asc:
156
+ sorted_good_records = sorted(records_good, key=lambda r: r.lap_time_seconds, reverse=False)
157
+ else:
158
+ sorted_good_records = sorted(records_good, key=lambda r: r.lap_time_seconds, reverse=True)
159
+
160
+ for j in sorted_good_records:
161
+ print(j)
162
+
163
+ if records_bad:
164
+ print(f"---------RACE REPORT OF INVALID RECORDS--------------")
165
+ for j in records_bad:
166
+ print(j)
167
+
168
+
169
+ def main_run():
170
+
171
+ #folder = '/Users/oleksiisaiun/Documents/ALESHA/REPO/PYCHARM/GIT_FOXMINDED_LABS/TRAINING_PYTHON/python_lab_6_oop_monaco_report/data'
172
+ folder = 'data'
173
+ file_abbr = 'abbreviations.txt'
174
+ file_start = 'start.log'
175
+ file_stop = 'end.log'
176
+
177
+
178
+ data_report = build_report(folder=folder, file_abbr=file_abbr, file_start=file_start, file_stop=file_stop)
179
+ print_report(records_good=data_report[0], records_bad=data_report[1],sort_by_lap_asc=False)
180
+
181
+
182
+ if __name__ == '__main__':
183
+ main_run()
@@ -0,0 +1,43 @@
1
+ import os
2
+ from datetime import datetime
3
+ from race_report import RecordData
4
+ from constants import ABBR_ROW_PATTERN,START_STOP_ROW_PATTERN,ERR_MSG__INVALID_FORMAT_OF_TIME_EVENT_ROW
5
+
6
+ def is_valid_datetime(value: str, datetime_format: str = "%Y-%m-%d_%H:%M:%S.%f") -> bool:
7
+ try:
8
+ datetime.strptime(value, datetime_format)
9
+ return True
10
+ except ValueError:
11
+ return False
12
+
13
+ def validate_if_file_exists(filepath) -> bool:
14
+ """Check if the folder and file exist, raise error if not."""
15
+ if not os.path.isfile(filepath):
16
+ raise FileNotFoundError(f"File not found: {filepath}")
17
+
18
+ folder = os.path.dirname(filepath)
19
+ if folder and not os.path.exists(folder):
20
+ raise FileNotFoundError(f"Folder does not exist: {folder}")
21
+ return True
22
+
23
+
24
+ def validate_abbreviation_row(row: str) -> RecordData:
25
+ match = ABBR_ROW_PATTERN.match(row)
26
+ if match:
27
+ driver_entry = RecordData(abbr=match.group('abbr'), driver=match.group('driver'), team=match.group('team'))
28
+ return driver_entry
29
+
30
+ return None
31
+
32
+ def validate_start_stop_row(row: str) -> (str, datetime):
33
+ match = START_STOP_ROW_PATTERN.match(row)
34
+ if match:
35
+ abbr = match.group('abbr')
36
+ time_event_raw=match.group('time_event')
37
+ if is_valid_datetime(time_event_raw):
38
+ time_event = datetime.strptime(time_event_raw, "%Y-%m-%d_%H:%M:%S.%f")
39
+ event_time_out = (abbr, time_event)
40
+ return event_time_out
41
+
42
+ print(f"discard row: [{row}], because {ERR_MSG__INVALID_FORMAT_OF_TIME_EVENT_ROW}")
43
+ return None # if start or stop row has invalid then a row is discarded
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: oleksiisaiun_lab6_monaco
3
+ Version: 1.0
4
+ Summary: The Python application that implements Monaco Racing Report in OOP format
5
+ Author-email: Oleksii Saiun <oleksiisaiun@example.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: black==25.1.0
14
+ Requires-Dist: click==8.1.8
15
+ Requires-Dist: exceptiongroup==1.2.2
16
+ Requires-Dist: flake8==7.1.2
17
+ Requires-Dist: iniconfig==2.1.0
18
+ Requires-Dist: mccabe==0.7.0
19
+ Requires-Dist: mypy-extensions==1.0.0
20
+ Requires-Dist: packaging==24.2
21
+ Requires-Dist: pathspec==0.12.1
22
+ Requires-Dist: platformdirs==4.3.7
23
+ Requires-Dist: pluggy==1.5.0
24
+ Requires-Dist: pycodestyle==2.12.1
25
+ Requires-Dist: pyflakes==3.2.0
26
+ Requires-Dist: pytest==8.3.5
27
+ Requires-Dist: tomli==2.2.1
28
+ Requires-Dist: typing-extensions==4.13.0
29
+ Dynamic: license-file
30
+
31
+ # Python_Lab_6_OOP_monaco_report
32
+ It's the Python app that implements Monaco Racing Report in OOP format
33
+
34
+
35
+ # How publish a package to PyPi using uv from a local computer:
36
+ #1. Go to root folder of this package:
37
+ #2. Run command
38
+ uv build
39
+ uv publish --token pypi-AgEIcHlwaS5vcmcCJGRhNmIxOTBlLTg3NTUtNGNmZi1iN2IyLWJkNGIwYWUxZjA2MwACKlszLCIzM2M1MmJiZS01NzhhLTQ5NGYtYjlmOS1hYzQzZTI4NTVkZTQiXQAABiDaegtzwu6CCdvCLNhLmzk9layeLZeP5-pYbnmR9aZYLA
@@ -0,0 +1,12 @@
1
+ oleksiisaiun_lab6_monaco/__init__.py,sha256=sODP91Uw0rmjnK00ZzhgDUPm-SxlNiTdc_2OBDFPiD4,56
2
+ oleksiisaiun_lab6_monaco/constants.py,sha256=AhfKQOCkcLiILoQ5vf9CD-oBoPMSrXIaPiQhegg5tAw,504
3
+ oleksiisaiun_lab6_monaco/race_report.py,sha256=lyahDK62FCEsHmt6DfGJabyK63Z4643spdzsKiHfVs8,6224
4
+ oleksiisaiun_lab6_monaco/utilities.py,sha256=c3BCr1RdAsvCGNUhQW-Rx4eZszDGjFsIs_POL5BXs3M,1628
5
+ oleksiisaiun_lab6_monaco/data/abbreviations.txt,sha256=3GAAVS7XOzXvfIC90WS8pEuCKtavQwGHV97do8xJ20o,663
6
+ oleksiisaiun_lab6_monaco/data/end.log,sha256=4wE9PQZfaPeFOfKGf24XUCiNfVe2DJwbOzyDuIt7FNs,513
7
+ oleksiisaiun_lab6_monaco/data/start.log,sha256=rKe7QZzeUVN8j4KVzs11sFfGtGZUw2Zuz3z-1RA9gv8,514
8
+ oleksiisaiun_lab6_monaco-1.0.dist-info/licenses/LICENSE,sha256=SLpkaez9xkCMD5ce3l3sJM59kS962nWIiMb-jdywXlg,1070
9
+ oleksiisaiun_lab6_monaco-1.0.dist-info/METADATA,sha256=4gT_KaSHuTGkh_wva154L_lmDZ1OrMDpwBsC_mu7vy8,1418
10
+ oleksiisaiun_lab6_monaco-1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
11
+ oleksiisaiun_lab6_monaco-1.0.dist-info/top_level.txt,sha256=M7B-GnGgDZrj10nblmrJGJMRFcOZ7d2hU_J_mijACfQ,25
12
+ oleksiisaiun_lab6_monaco-1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Oleksii Saiun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ oleksiisaiun_lab6_monaco