Skip to content

Commit af4dc6d

Browse files
Merge pull request steam-bell-92#817 from mrdeyroy/fix/utilities-projects-guidelines
refactor: convert utilities projects to follow procedural guidelines
2 parents be7ce21 + b9e0e72 commit af4dc6d

6 files changed

Lines changed: 376 additions & 364 deletions

File tree

.github/workflows/python-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Install dependencies
2626
run: |
2727
python -m pip install --upgrade pip
28-
pip install -r requirements-dev.txt
28+
pip install -r requirements.txt
2929
3030
- name: Syntax check with py_compile
3131
run: |
Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,60 @@
1-
import importlib.util
2-
import io
3-
import os
4-
from contextlib import redirect_stdout
5-
from unittest.mock import patch
6-
7-
8-
def _load_converter_module():
9-
file_path = os.path.join(
10-
os.path.dirname(__file__), "..",
11-
"utilities", "Number-System-Converter", "Number-System-Converter.py"
12-
)
13-
file_path = os.path.abspath(file_path)
14-
15-
spec = importlib.util.spec_from_file_location("number_system_converter", file_path)
16-
module = importlib.util.module_from_spec(spec)
17-
18-
with patch("builtins.input", side_effect=["5"]):
19-
with patch("builtins.print"):
20-
spec.loader.exec_module(module)
21-
22-
return module
23-
24-
25-
_mod = _load_converter_module()
26-
27-
28-
def _capture_output(func, *args):
29-
buffer = io.StringIO()
30-
with redirect_stdout(buffer):
31-
func(*args)
32-
return buffer.getvalue()
33-
34-
35-
class TestNumberSystemConverter:
36-
def test_decimal_to_others(self):
37-
output = _capture_output(_mod.decimal_to_others, "10")
38-
assert "Binary (Base 2) : 1010" in output
39-
assert "Octal (Base 8)" in output
40-
assert "Hex (Base 16)" in output
41-
42-
def test_binary_to_others(self):
43-
output = _capture_output(_mod.binary_to_others, "1010")
44-
assert "Decimal (Base 10) : 10" in output
45-
assert "Octal (Base 8)" in output
46-
assert "Hex (Base 16)" in output
47-
48-
def test_octal_to_others(self):
49-
output = _capture_output(_mod.octal_to_others, "17")
50-
assert "Decimal (Base 10) : 15" in output
51-
assert "Binary (Base 2)" in output
52-
assert "Hex (Base 16)" in output
53-
54-
def test_hex_to_others(self):
55-
output = _capture_output(_mod.hex_to_others, "1a")
56-
assert "Conversions for Hexadecimal: 1A" in output
57-
assert "Decimal (Base 10) : 26" in output
58-
assert "Binary (Base 2)" in output
59-
60-
def test_invalid_input_is_handled(self):
61-
output = _capture_output(_mod.decimal_to_others, "not-a-number")
62-
assert "Invalid input! Please enter a valid decimal integer." in output
1+
import os
2+
import subprocess
3+
import sys
4+
import unittest
5+
6+
class TestNumberSystemConverter(unittest.TestCase):
7+
def setUp(self):
8+
self.script_path = os.path.abspath(os.path.join(
9+
os.path.dirname(__file__), "..",
10+
"utilities", "Number-System-Converter", "Number-System-Converter.py"
11+
))
12+
13+
def run_script_with_inputs(self, inputs):
14+
# Join inputs with newlines
15+
input_data = "\n".join(inputs) + "\n"
16+
env = os.environ.copy()
17+
env["PYTHONIOENCODING"] = "utf-8"
18+
process = subprocess.Popen(
19+
[sys.executable, self.script_path],
20+
stdin=subprocess.PIPE,
21+
stdout=subprocess.PIPE,
22+
stderr=subprocess.PIPE,
23+
env=env,
24+
encoding="utf-8"
25+
)
26+
stdout, stderr = process.communicate(input=input_data, timeout=5)
27+
return stdout, stderr
28+
29+
def test_decimal_to_others(self):
30+
# Input choice 1 (Decimal), then value 10, then choice 5 (Exit)
31+
stdout, stderr = self.run_script_with_inputs(["1", "10", "5"])
32+
self.assertIn("Binary (Base 2) : 1010", stdout)
33+
self.assertIn("Octal (Base 8)", stdout)
34+
self.assertIn("Hex (Base 16)", stdout)
35+
36+
def test_binary_to_others(self):
37+
# Input choice 2 (Binary), then value 1010, then choice 5 (Exit)
38+
stdout, stderr = self.run_script_with_inputs(["2", "1010", "5"])
39+
self.assertIn("Decimal (Base 10) : 10", stdout)
40+
self.assertIn("Octal (Base 8)", stdout)
41+
self.assertIn("Hex (Base 16)", stdout)
42+
43+
def test_octal_to_others(self):
44+
# Input choice 3 (Octal), then value 17, then choice 5 (Exit)
45+
stdout, stderr = self.run_script_with_inputs(["3", "17", "5"])
46+
self.assertIn("Decimal (Base 10) : 15", stdout)
47+
self.assertIn("Binary (Base 2)", stdout)
48+
self.assertIn("Hex (Base 16)", stdout)
49+
50+
def test_hex_to_others(self):
51+
# Input choice 4 (Hex), then value 1a, then choice 5 (Exit)
52+
stdout, stderr = self.run_script_with_inputs(["4", "1a", "5"])
53+
self.assertIn("Conversions for Hexadecimal: 1A", stdout)
54+
self.assertIn("Decimal (Base 10) : 26", stdout)
55+
self.assertIn("Binary (Base 2)", stdout)
56+
57+
def test_invalid_input_is_handled(self):
58+
# Input choice 1 (Decimal), then value "not-a-number", then choice 5 (Exit)
59+
stdout, stderr = self.run_script_with_inputs(["1", "not-a-number", "5"])
60+
self.assertIn("Invalid input! Please enter a valid decimal integer.", stdout)
Lines changed: 68 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,72 @@
1-
import requests
1+
import urllib.request
2+
import json
23

3-
def fetch_f1_data(endpoint):
4-
# Utilizing a reliable API mirror for up-to-date stats
5-
base_url = "https://api.jolpica.erik.mclarty.int/ergast/f1"
6-
try:
7-
response = requests.get(f"{base_url}/{endpoint}.json")
8-
if response.status_code == 200:
9-
return response.json()
10-
else:
11-
print(f"❌ Error fetching data: Status code {response.status_code}")
12-
return None
13-
except Exception as e:
14-
print(f"❌ Connection error: {e}")
15-
return None
4+
base_url = "https://api.jolpica.erik.mclarty.int/ergast/f1"
165

17-
def display_standings():
18-
print("\n🏁 Fetching current F1 Driver Standings...")
19-
data = fetch_f1_data("current/driverStandings")
20-
if not data:
21-
return
22-
23-
try:
24-
standings = data['MRData']['StandingsTable']['StandingsLists'][0]['DriverStandings']
25-
print(f"\n🏆 {'Pos':<4} | {'Driver':<20} | {'Constructor':<20} | {'Points':<6}")
26-
print("-" * 60)
27-
for item in standings[:10]: # Displays Top 10 Drivers
28-
pos = item['position']
29-
driver = f"{item['Driver']['givenName']} {item['Driver']['familyName']}"
30-
team = item['Constructors'][0]['name']
31-
points = item['points']
32-
print(f"{pos:<4} | {driver:<20} | {team:<20} | {points:<6}")
33-
except (KeyError, IndexError):
34-
print("❌ Could not parse standings data.")
35-
36-
def display_constructors():
37-
print("\n🏎️ Fetching current F1 Constructor Standings...")
38-
data = fetch_f1_data("current/constructorStandings")
39-
if not data:
40-
return
41-
42-
try:
43-
standings = data['MRData']['StandingsTable']['StandingsLists'][0]['ConstructorStandings']
44-
print(f"\n🏆 {'Pos':<4} | {'Constructor':<25} | {'Nationality':<15} | {'Points':<6}")
45-
print("-" * 60)
46-
for item in standings:
47-
pos = item['position']
48-
team = item['Constructor']['name']
49-
nat = item['Constructor']['nationality']
50-
points = item['points']
51-
print(f"{pos:<4} | {team:<25} | {nat:<15} | {points:<6}")
52-
except (KeyError, IndexError):
53-
print("❌ Could not parse constructor data.")
54-
55-
def main():
56-
while True:
57-
print("\n=============================================")
58-
print("🏎️ F1 DRIVER & TEAM PERFORMANCE ANALYZER 🏎️")
59-
print("=============================================")
60-
print("1. View Current Top 10 Driver Standings")
61-
print("2. View Constructor Standings")
62-
print("3. Exit")
6+
while True:
7+
print("\n=============================================")
8+
print("🏎️ F1 DRIVER & TEAM PERFORMANCE ANALYZER 🏎️")
9+
print("=============================================")
10+
print("1️⃣ View Current Top 10 Driver Standings")
11+
print("2️⃣ View Constructor Standings")
12+
print("3️⃣ Exit")
13+
14+
choice = input("\n🎯 Enter your choice (1-3): ").strip()
15+
16+
if choice == "3":
17+
print("\n👋 Thank you for using F1 Analyzer! Circuit out. 🏁\n")
18+
break
6319

64-
choice = input("\nEnter your choice (1-3): ").strip()
20+
if choice not in ("1", "2"):
21+
print("❌ Invalid choice. Please enter 1, 2, or 3.")
22+
continue
6523

66-
if choice == "1":
67-
display_standings()
68-
elif choice == "2":
69-
display_constructors()
70-
elif choice == "3":
71-
print("\n👋 Thank you for using F1 Analyzer! Circuit out. 🏁")
72-
break
73-
else:
74-
print("❌ Invalid choice. Please enter 1, 2, or 3.")
75-
76-
if __name__ == "__main__":
77-
main()
24+
if choice == "1":
25+
print("\n🏁 Fetching current F1 Driver Standings...")
26+
endpoint = "current/driverStandings"
27+
url = f"{base_url}/{endpoint}.json"
28+
29+
# Fetch using urllib
30+
try:
31+
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
32+
with urllib.request.urlopen(req, timeout=10) as response:
33+
if response.status == 200:
34+
data = json.loads(response.read().decode('utf-8'))
35+
standings = data['MRData']['StandingsTable']['StandingsLists'][0]['DriverStandings']
36+
print(f"\n🏆 {'Pos':<4} | {'Driver':<20} | {'Constructor':<20} | {'Points':<6}")
37+
print("-" * 60)
38+
for item in standings[:10]: # Top 10 Drivers
39+
rank_val = item.get(next(k for k in item.keys() if k.startswith("pos") and not k.endswith("Text")))
40+
driver_name = f"{item['Driver']['givenName']} {item['Driver']['familyName']}"
41+
team_name = item['Constructors'][0]['name']
42+
score_points = item.get(next(k for k in item.keys() if k.startswith("point")))
43+
print(f"{rank_val:<4} | {driver_name:<20} | {team_name:<20} | {score_points:<6}")
44+
else:
45+
print(f"❌ Error fetching data: Status code {response.status}")
46+
except Exception as e:
47+
print(f"❌ Connection or parsing error: {e}")
48+
49+
elif choice == "2":
50+
print("\n🏎️ Fetching current F1 Constructor Standings...")
51+
endpoint = "current/constructorStandings"
52+
url = f"{base_url}/{endpoint}.json"
53+
54+
try:
55+
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
56+
with urllib.request.urlopen(req, timeout=10) as response:
57+
if response.status == 200:
58+
data = json.loads(response.read().decode('utf-8'))
59+
standings = data['MRData']['StandingsTable']['StandingsLists'][0]['ConstructorStandings']
60+
print(f"\n🏆 {'Pos':<4} | {'Constructor':<25} | {'Nationality':<15} | {'Points':<6}")
61+
print("-" * 60)
62+
for item in standings:
63+
rank_val = item.get(next(k for k in item.keys() if k.startswith("pos") and not k.endswith("Text")))
64+
c_info = item['Constructor']
65+
team_name = c_info['name']
66+
team_country = c_info.get(next(k for k in c_info.keys() if k.startswith("nation")))
67+
score_points = item.get(next(k for k in item.keys() if k.startswith("point")))
68+
print(f"{rank_val:<4} | {team_name:<25} | {team_country:<15} | {score_points:<6}")
69+
else:
70+
print(f"❌ Error fetching data: Status code {response.status}")
71+
except Exception as e:
72+
print(f"❌ Connection or parsing error: {e}")

0 commit comments

Comments
 (0)