diff --git a/Practice Assignment 1/main.py b/Practice Assignment 1/main.py index 3141eba..1a5bd42 100755 --- a/Practice Assignment 1/main.py +++ b/Practice Assignment 1/main.py @@ -17,7 +17,6 @@ def transcript(text: str) -> str: ) ) - class Rot13App(App): """A Textual app for ROT13 encryption and decryption.""" diff --git a/Practice Assignment 2/main.py b/Practice Assignment 2/main.py new file mode 100755 index 0000000..5912bfd --- /dev/null +++ b/Practice Assignment 2/main.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +import os +import sys +from collections import defaultdict +from datetime import datetime + +# Constants for fitness levels +FITNESS_LEVELS = { + (0, 9.0): "Fitness level below pace for long games", + (9.1, 10.0): "Fitness level for amateur player", + (10.1, 11.0): "Fitness level for above average amateur player", + (11.1, 12.0): "Fitness level for elite player", + (12.1, 13.0): "Very high fitness level" +} + + +class ValidationError(Exception): + pass + + +class PlayerFitnessData: + def __init__(self, date, player, level, laps, speed, lap_time): + self.date = date + self.player = player + self.level = level + self.laps = laps + self.speed = speed + self.lap_time = lap_time + + def __repr__(self): + return f"PlayerFitnessData({self.player}, {self.date})" + + +class PlayerFitnessAnalyzer: + def __init__(self, data_file): + self.data_file = data_file + self.player_data = defaultdict(list) + self.errors = [] + + def validate_data(self, date, player, level, laps, speed, lap_time): + # Validate date format (DD Mon) + if not isinstance(date, str) or len(date.split()) != 2: + raise ValidationError(f"Invalid date format: {date}. Expected 'DD Mon'") + + if not isinstance(player, str) or not player.strip(): + raise ValidationError("Player name cannot be empty") + + try: + level = float(level) + if level < 0 or level > 13: + raise ValidationError(f"Level {level} out of range (0-13)") + except ValueError: + raise ValidationError(f"Level must be numeric: {level}") + + try: + laps = int(laps) + if laps <= 0: + raise ValidationError(f"Laps must be positive: {laps}") + except ValueError: + raise ValidationError(f"Laps must be integer: {laps}") + + try: + speed = float(speed) + if speed <= 0: + raise ValidationError(f"Speed must be positive: {speed}") + except ValueError: + raise ValidationError(f"Speed must be numeric: {speed}") + + try: + lap_time = float(lap_time) + if lap_time <= 0: + raise ValidationError(f"Lap time must be positive: {lap_time}") + except ValueError: + raise ValidationError(f"Lap time must be numeric: {lap_time}") + + return level, laps, speed, lap_time + + def load_data(self): + """ + Load and parse player data from file. + + Expected format (space/tab separated): + Date Player Level Laps Speed Lap Time(s) + 11 Jan Harry Keane 9 11 12.5 5.76 + ... + """ + if not os.path.exists(self.data_file): + raise FileNotFoundError(f"Data file not found: {self.data_file}") + + try: + with open(self.data_file, 'r') as f: + lines = f.readlines() + except IOError as e: + raise IOError(f"Cannot read file {self.data_file}: {e}") + + if not lines: + raise ValidationError("Data file is empty") + + # Skip header line if present + start_line = 0 + if lines[0].strip().lower().startswith('date'): + start_line = 1 + + for line_num, line in enumerate(lines[start_line:], start=start_line + 1): + line = line.strip() + + if not line or line.startswith('#'): + continue + + try: + parts = line.split() + + if len(parts) < 8: + self.errors.append(f"Line {line_num}: Not enough fields: {line}") + continue + + date = f"{parts[0]} {parts[1]}" + player = f"{parts[2]} {parts[3]}" + level_str = parts[4] + laps_str = parts[5] + speed_str = parts[6] + lap_time_str = parts[7] + + level, laps, speed, lap_time = self.validate_data( + date, player, level_str, laps_str, speed_str, lap_time_str + ) + + data = PlayerFitnessData(date, player, level, laps, speed, lap_time) + self.player_data[player].append(data) + + except ValidationError as e: + self.errors.append(f"Line {line_num}: {str(e)}") + except (IndexError, ValueError) as e: + self.errors.append(f"Line {line_num}: Parse error: {str(e)}") + + def get_fitness_level_description(self, average_level): + for (min_val, max_val), description in FITNESS_LEVELS.items(): + if min_val <= average_level <= max_val: + return description + return "Unknown fitness level" + + def analyze_player(self, player_name, weeks=4): + if player_name not in self.player_data: + return None + + data_list = self.player_data[player_name] + + if len(data_list) < weeks: + recent_data = data_list + weeks_used = len(data_list) + else: + recent_data = data_list[-weeks:] + weeks_used = weeks + + if not recent_data: + return None + + avg_level = sum(d.level for d in recent_data) / len(recent_data) + avg_laps = sum(d.laps for d in recent_data) / len(recent_data) + avg_speed = sum(d.speed for d in recent_data) / len(recent_data) + avg_lap_time = sum(d.lap_time for d in recent_data) / len(recent_data) + + return { + 'player': player_name, + 'weeks_analyzed': weeks_used, + 'average_level': round(avg_level, 2), + 'average_laps': round(avg_laps, 2), + 'average_speed': round(avg_speed, 2), + 'average_lap_time': round(avg_lap_time, 2), + 'fitness_description': self.get_fitness_level_description(avg_level) + } + + def generate_report(self): + if not self.player_data: + print("No player data loaded.") + return + + print("\n" + "=" * 80) + print("PENDLETOWN FS - MONTHLY FITNESS REPORT") + print("=" * 80) + + for player_name in sorted(self.player_data.keys()): + summary = self.analyze_player(player_name) + + if summary: + print(f"\nPlayer: {summary['player']}") + print("-" * 80) + print(f" Weeks Analyzed: {summary['weeks_analyzed']}") + print(f" Average Level: {summary['average_level']:.2f}") + print(f" Average Laps: {summary['average_laps']:.2f}") + print(f" Average Speed: {summary['average_speed']:.2f} km/h") + print(f" Average Lap Time: {summary['average_lap_time']:.2f} seconds") + print(f"\n Fitness Level: {summary['fitness_description']}") + + print("\n" + "=" * 80 + "\n") + + def print_errors(self): + if self.errors: + print("\n" + "!" * 80) + print("DATA VALIDATION ERRORS:") + print("!" * 80) + for error in self.errors: + print(f" {error}") + print("!" * 80 + "\n") + return True + return False + + +def main(): + data_file = "player_data.txt" + + if len(sys.argv) > 1: + data_file = sys.argv[1] + + print(f"\nLoading player data from: {data_file}") + + try: + analyzer = PlayerFitnessAnalyzer(data_file) + analyzer.load_data() + + has_errors = analyzer.print_errors() + + if analyzer.player_data: + analyzer.generate_report() + if has_errors: + print("Note: Some lines had errors but valid data was processed.\n") + else: + print("No valid data to process.") + sys.exit(1) + + except FileNotFoundError as e: + print(f"ERROR: {e}") + sys.exit(1) + except IOError as e: + print(f"ERROR: {e}") + sys.exit(1) + except ValidationError as e: + print(f"ERROR: {e}") + sys.exit(1) + except Exception as e: + print(f"UNEXPECTED ERROR: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Practice Assignment 2/player_data.txt b/Practice Assignment 2/player_data.txt new file mode 100644 index 0000000..6ff9fe2 --- /dev/null +++ b/Practice Assignment 2/player_data.txt @@ -0,0 +1,21 @@ +Date Player Level Laps Speed Lap Time(s) +11 Jan Harry Keane 9 11 12.5 5.76 +11 Jan Nat Sturridge 8 10 11.5 6.55 +11 Jan Dwayne Vardy 10 11 13 5.54 +11 Jan Kyle Baker 8 10 12 6 +11 Jan Elliott Ramsey 10 11 13 5.54 +18 Jan Harry Keane 9.2 11 12.6 5.74 +18 Jan Nat Sturridge 8.1 10 11.6 6.52 +18 Jan Dwayne Vardy 10.1 11 13.1 5.53 +18 Jan Kyle Baker 8.3 11 12.2 5.95 +18 Jan Elliott Ramsey 10.2 11 13.1 5.52 +25 Jan Harry Keane 9.4 12 12.8 5.70 +25 Jan Nat Sturridge 8.3 11 11.8 6.48 +25 Jan Dwayne Vardy 10.3 12 13.2 5.50 +25 Jan Kyle Baker 8.5 11 12.4 5.90 +25 Jan Elliott Ramsey 10.4 12 13.2 5.50 +01 Feb Harry Keane 9.6 12 13 5.68 +01 Feb Nat Sturridge 8.5 11 12 6.45 +01 Feb Dwayne Vardy 10.5 12 13.3 5.48 +01 Feb Kyle Baker 8.7 12 12.6 5.85 +01 Feb Elliott Ramsey 10.6 12 13.3 5.48 \ No newline at end of file diff --git a/test/.python-version b/test/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/test/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..e69de29 diff --git a/test/main.py b/test/main.py new file mode 100755 index 0000000..2598e7f --- /dev/null +++ b/test/main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import typer +from typing import Annotated + +app = typer.Typer() + +@app.command() +def goodbye(name: Annotated[str, typer.Option(prompt="What is your name?")] = "World"): + typer.echo(f"goodbye {name}") + +@app.command() +def main(): + typer.echo(f"Hello") + +if __name__ == "__main__": + app() \ No newline at end of file diff --git a/test/pyproject.toml b/test/pyproject.toml new file mode 100644 index 0000000..f7e1818 --- /dev/null +++ b/test/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "test" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [ + { name = "Uthman Fatih", email = "kingvcheese@gmail.com" } +] +requires-python = ">=3.14" +dependencies = [ + "typer>=0.27.2", +] + +[project.scripts] +test = "test:main" + +[build-system] +requires = ["uv_build>=0.12.7,<0.13.0"] +build-backend = "uv_build" diff --git a/test/src/test/__init__.py b/test/src/test/__init__.py new file mode 100644 index 0000000..7bf258d --- /dev/null +++ b/test/src/test/__init__.py @@ -0,0 +1,2 @@ +def main() -> None: + print("Hello from test!") diff --git a/test/uv.lock b/test/uv.lock new file mode 100644 index 0000000..ad23fbe --- /dev/null +++ b/test/uv.lock @@ -0,0 +1,99 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "test" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "typer" }, +] + +[package.metadata] +requires-dist = [{ name = "typer", specifier = ">=0.27.2" }] + +[[package]] +name = "typer" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" }, +]