diff --git a/Currency Script/main.py b/Currency Script/main.py index ed705b92..f3643030 100644 --- a/Currency Script/main.py +++ b/Currency Script/main.py @@ -1,2 +1,38 @@ -# TODO IMPORT API / CONVERTER / CURRENCY MODULES -# TODO UPDATE / POPULATE MAIN TO ENSURE THERE IS A INTUITIVE SIMPLE UI \ No newline at end of file +""" +main.py - Simple CLI interface for Currency Converter + +Prompts user for input currencies and amount, and displays the converted value. +""" + +from converter import CurrencyConverter + +def main(): + """ + Run the interactive currency converter CLI loop. + Prompts user for source and target currencies and amount to convert. + Continues until the user types 'no' to exit. + """ + print("Welcome to the Currency Converter\n") + + converter = CurrencyConverter() + + while True: + try: + from_currency = input("Enter source currency code (e.g., AUD): ").strip().upper() + to_currency = input("Enter target currency code (e.g., USD): ").strip().upper() + amount = float(input(f"Enter amount in {from_currency}: ")) + + converted = converter.convert(from_currency, to_currency, amount) + print(f"\nāœ… {amount} {from_currency} = {converted} {to_currency}\n") + + except ValueError as e: + print(f"āŒ Error: {e}\n") + + # Ask to convert another or exit + again = input("Do you want to convert another amount? (yes/no): ").strip().lower() + if again != "yes": + print("Thank you for using the Currency Converter. Goodbye!") + break + +if __name__ == "__main__": + main() diff --git a/Currency Script/readme.md b/Currency Script/readme.md new file mode 100644 index 00000000..8fed1611 --- /dev/null +++ b/Currency Script/readme.md @@ -0,0 +1,57 @@ +Currency Converter +================== + +A simple Python CLI application that converts an amount from one currency to another +using live exchange rates fetched from an open API. + +Modules: +-------- + +1. **api_handler.py** + - Handles fetching exchange rate data from the online API. + +2. **currencies.py** + - Provides utility functions to list supported currencies and validate currency codes. + +3. **converter.py** + - Contains the core logic to convert between currencies using the exchange rates. + +4. **main.py** + - CLI interface where users input the currencies and amount to convert. + +5. **test_*.py** + - Unit tests for each module to ensure functionality. + +Requirements: +------------- +- Python 3.7+ +- `requests` library + +Install dependencies: +--------------------- +Run the following command to install dependencies: +- pip install -r requirements.txt + +How to Use: +----------- +Run the main script from the terminal: + +Follow the prompts to enter: +- Source currency code (e.g., USD) +- Target currency code (e.g., AUD) +- Amount to convert + +The app will display the converted amount and allow you to repeat or exit. + +Testing: +-------- +To run tests: +- python -m unittest discover + +Note: +----- +The conversion is based on live exchange rates retrieved from: +https://open.er-api.com/v6/latest + +Make sure you are connected to the internet when running the app. + diff --git a/Currency Script/readme.txt b/Currency Script/readme.txt deleted file mode 100644 index 3202240d..00000000 --- a/Currency Script/readme.txt +++ /dev/null @@ -1 +0,0 @@ -# TODO - UPDATE README \ No newline at end of file diff --git a/Currency Script/requirements.txt b/Currency Script/requirements.txt index ef92400b..663bd1f6 100644 --- a/Currency Script/requirements.txt +++ b/Currency Script/requirements.txt @@ -1 +1 @@ -# TODO - UPDATE REQUIREMENTS TO NOTE INSTALLS ARE NOTED \ No newline at end of file +requests \ No newline at end of file diff --git a/Currency Script/setup.py b/Currency Script/setup.py index d1322786..de3a6108 100644 --- a/Currency Script/setup.py +++ b/Currency Script/setup.py @@ -1 +1,9 @@ -# UPDATE SETUP TO NOTE SET UP / PACKAGES \ No newline at end of file +from setuptools import setup + +setup( + name="currency_converter", + version="0.1", + install_requires=[ + "requests" + ], +) \ No newline at end of file diff --git a/Currency Script/src/api_handler.py b/Currency Script/src/api_handler.py index 6f3ed23a..8a8f192f 100644 --- a/Currency Script/src/api_handler.py +++ b/Currency Script/src/api_handler.py @@ -1,15 +1,23 @@ import requests def get_exchange_data(api_url: str = "https://open.er-api.com/v6/latest") -> dict: - """Fetch latest exchange data from the API.""" + """ + Retrieve the latest foreign exchange rates from the specified API. + + Args: + api_url (str): Endpoint to fetch exchange data from. Defaults to the open.er-api URL. + + Returns: + dict: A dictionary containing the base currency, timestamp, and exchange rates. + + Raises: + Exception: If the API request fails or returns a non-200 status. + """ response = requests.get(api_url) if response.status_code != 200: raise Exception(f"API request failed with status {response.status_code}") data = response.json() - # Ensure response was successful - if data.get("result") != "success": - raise Exception(f"API returned error: {data.get('error-type', 'Unknown error')}") return data # Includes 'base_code', 'time_last_update_utc', and 'rates' diff --git a/Currency Script/src/converter.py b/Currency Script/src/converter.py index faa21d69..df03f702 100644 --- a/Currency Script/src/converter.py +++ b/Currency Script/src/converter.py @@ -1,7 +1,3 @@ -""" -CurrencyConverter: Converts an amount from one currency to another using live exchange rates. -""" - from api_handler import get_exchange_data class CurrencyConverter: @@ -35,7 +31,8 @@ def convert(self, from_currency: str, to_currency: str, amount: float) -> float: converted_amount = round(amount_in_base * self.rates[to_currency], 2) return converted_amount -# --- DEBUG / MANUAL TEST --- +# --- DEBUG / MANUAL TEST SECTION --- +# This section runs only when you run this file directly (not when imported elsewhere) if __name__ == "__main__": print("Running manual test for CurrencyConverter...\n") diff --git a/Currency Script/src/currencies.py b/Currency Script/src/currencies.py index 1c387a4f..ba5be173 100644 --- a/Currency Script/src/currencies.py +++ b/Currency Script/src/currencies.py @@ -1,11 +1,28 @@ from api_handler import get_exchange_data -# Returns a list of all supported currency codes. def get_supported_currencies(rates): + """ + Return a list of supported currency codes from the rates dictionary. + + Args: + rates (dict): Dictionary of currency codes and their exchange rates. + + Returns: + list: List of supported currency codes (e.g., ['USD', 'EUR']). + """ return list(rates.keys()) -# Checks if a currency code is supported. def is_valid_currency(currency_code, rates): + """ + Check if the given currency code is valid and supported. + + Args: + currency_code (str): Currency code to validate (e.g., 'USD'). + rates (dict): Dictionary of available exchange rates. + + Returns: + bool: True if the currency code exists in the rates, False otherwise. + """ return currency_code.upper() in rates # --- DEBUG / MANUAL TEST SECTION --- diff --git a/Currency Script/tests/__init__.py b/Currency Script/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Currency Script/tests/__pycache__/__init__.cpython-313.pyc b/Currency Script/tests/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..52c26ecd Binary files /dev/null and b/Currency Script/tests/__pycache__/__init__.cpython-313.pyc differ diff --git a/Currency Script/tests/__pycache__/test_api_handler.cpython-313-pytest-8.3.5.pyc b/Currency Script/tests/__pycache__/test_api_handler.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 00000000..adca8b35 Binary files /dev/null and b/Currency Script/tests/__pycache__/test_api_handler.cpython-313-pytest-8.3.5.pyc differ diff --git a/Currency Script/tests/__pycache__/test_converter.cpython-313-pytest-8.3.5.pyc b/Currency Script/tests/__pycache__/test_converter.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 00000000..020a50ff Binary files /dev/null and b/Currency Script/tests/__pycache__/test_converter.cpython-313-pytest-8.3.5.pyc differ diff --git a/Currency Script/tests/__pycache__/test_currencies.cpython-313-pytest-8.3.5.pyc b/Currency Script/tests/__pycache__/test_currencies.cpython-313-pytest-8.3.5.pyc new file mode 100644 index 00000000..adf1456a Binary files /dev/null and b/Currency Script/tests/__pycache__/test_currencies.cpython-313-pytest-8.3.5.pyc differ diff --git a/Currency Script/tests/test_api_handler.py b/Currency Script/tests/test_api_handler.py new file mode 100644 index 00000000..50faa775 --- /dev/null +++ b/Currency Script/tests/test_api_handler.py @@ -0,0 +1,37 @@ +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) + +import unittest +from unittest.mock import patch +from src.api_handler import get_exchange_data + +class TestAPIHandler(unittest.TestCase): + """ + Unit tests for the get_exchange_data function in api_handler.py. + """ + @patch("api_handler.requests.get") + def test_get_exchange_data_success(self, mock_get): + mock_get.return_value.status_code = 200 + mock_get.return_value.json.return_value = { + "base": "EUR", + "date": "2025-06-02", + "rates": {"USD": 1.14, "GBP": 0.84} + } + + data = get_exchange_data() + self.assertEqual(data["base"], "EUR") + self.assertIn("USD", data["rates"]) + + @patch("api_handler.requests.get") + def test_get_exchange_data_failure(self, mock_get): + """ + Test that get_exchange_data raises an exception when API response fails. + """ + mock_get.return_value.status_code = 404 + with self.assertRaises(Exception): + get_exchange_data() + +if __name__ == "__main__": + unittest.main() + diff --git a/Currency Script/tests/test_converter.py b/Currency Script/tests/test_converter.py new file mode 100644 index 00000000..c6c4707f --- /dev/null +++ b/Currency Script/tests/test_converter.py @@ -0,0 +1,33 @@ +import unittest +from converter import CurrencyConverter + +class TestCurrencyConverter(unittest.TestCase): + """Unit tests for CurrencyConverter class.""" + + @classmethod + def setUpClass(cls): + """Initialize converter once for all tests.""" + cls.converter = CurrencyConverter() + + def test_valid_conversion(self): + """Test converting from USD to EUR returns a float value.""" + result = self.converter.convert("USD", "EUR", 100) + self.assertIsInstance(result, float) + self.assertGreater(result, 0) + + def test_same_currency(self): + """Test converting a currency to itself returns the same amount.""" + amount = 50 + result = self.converter.convert("USD", "USD", amount) + self.assertAlmostEqual(result, amount, places=2) + + def test_invalid_currency(self): + """Test invalid currency codes raise ValueError.""" + with self.assertRaises(ValueError): + self.converter.convert("XXX", "EUR", 100) + + with self.assertRaises(ValueError): + self.converter.convert("USD", "YYY", 100) + +if __name__ == "__main__": + unittest.main() diff --git a/Currency Script/tests/test_currencies.py b/Currency Script/tests/test_currencies.py new file mode 100644 index 00000000..f79a1164 --- /dev/null +++ b/Currency Script/tests/test_currencies.py @@ -0,0 +1,33 @@ +import unittest +from currencies import get_supported_currencies, is_valid_currency + +class TestCurrencies(unittest.TestCase): + """Unit tests for currency helper functions.""" + + def setUp(self): + """Set up sample rates dictionary for use in tests.""" + self.sample_rates = { + "USD": 1.1, + "EUR": 1.0, + "GBP": 0.85, + "JPY": 150.0 + } + + def test_get_supported_currencies(self): + """Test that all currency codes are returned correctly.""" + expected = ["USD", "EUR", "GBP", "JPY"] + result = get_supported_currencies(self.sample_rates) + self.assertListEqual(sorted(result), sorted(expected)) + + def test_is_valid_currency_true(self): + """Test that valid currency codes return True.""" + self.assertTrue(is_valid_currency("usd", self.sample_rates)) + self.assertTrue(is_valid_currency("EUR", self.sample_rates)) + + def test_is_valid_currency_false(self): + """Test that unsupported currency codes return False.""" + self.assertFalse(is_valid_currency("ABC", self.sample_rates)) + self.assertFalse(is_valid_currency("xyz", self.sample_rates)) + +if __name__ == "__main__": + unittest.main()