Skip to content

Commit 8a75d4f

Browse files
authored
Merge pull request #8 from HarshitDalal/F-4_humanize_number
F 4 humanize number
2 parents 20dee6a + e17d726 commit 8a75d4f

File tree

11 files changed

+432
-22
lines changed

11 files changed

+432
-22
lines changed

NumWord/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from .number_to_word import NumberToWord
22
from .word_to_number import WordToNumber
3+
from .humanize_number import HumanizeNumber
4+
from .currency import Currency

NumWord/currency.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import os
2+
import json
3+
import requests
4+
from datetime import datetime
5+
6+
from utility.symboles import CURRENCY_SYMBOLS
7+
8+
9+
class Currency:
10+
__FILE_NAME = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'utility', 'exchange_rates.json')
11+
__BASE_CURRENCY = "USD"
12+
13+
def __init__(self):
14+
self.__load_or_update_rates()
15+
16+
def __fetch_exchange_rates(self):
17+
"""
18+
Fetch all exchange rates for the base currency from an API.
19+
20+
Returns:
21+
dict: A dictionary containing the date, rates, and base currency.
22+
23+
Raises:
24+
ConnectionError: If the API request fails or returns an error.
25+
"""
26+
url = f"https://open.er-api.com/v6/latest/{self.__BASE_CURRENCY}"
27+
response = requests.get(url)
28+
format_data = {}
29+
if response.status_code == 200:
30+
data = response.json()
31+
format_data["date"] = datetime.now().strftime("%Y-%m-%d")
32+
format_data["rates"] = data["rates"]
33+
format_data["Base"] = data["base_code"]
34+
return format_data
35+
else:
36+
raise ConnectionError("Failed to fetch exchange rates")
37+
38+
def __save_exchange_rates(self, data):
39+
"""
40+
Save exchange rates to a JSON file.
41+
42+
Args:
43+
data (dict): The exchange rates data to be saved.
44+
"""
45+
with open(self.__FILE_NAME, "w", encoding="utf-8") as file:
46+
json.dump(data, file, indent=4)
47+
48+
def __load_exchange_rates(self):
49+
"""
50+
Load exchange rates from file if valid.
51+
52+
Returns:
53+
dict|None: The loaded exchange rates data if valid, or `None` if not valid.
54+
"""
55+
if os.path.exists(self.__FILE_NAME):
56+
with open(self.__FILE_NAME, "r") as file:
57+
data = json.load(file)
58+
if data.get("date") == datetime.now().strftime("%Y-%m-%d"):
59+
return data
60+
return None
61+
62+
def __load_or_update_rates(self):
63+
"""
64+
Load exchange rates from file or update them by fetching from the API.
65+
"""
66+
data = self.__load_exchange_rates()
67+
if data is None:
68+
data = self.__fetch_exchange_rates()
69+
self.__save_exchange_rates(data)
70+
self.__rates = data["rates"]
71+
72+
def __get_exchange_rate(self, from_currency, to_currency):
73+
"""
74+
Get exchange rate from stored data.
75+
76+
Args:
77+
from_currency (str): The currency from which to convert.
78+
to_currency (str): The currency to which to convert.
79+
80+
Returns:
81+
float: The exchange rate for converting from `from_currency` to `to_currency`.
82+
83+
Raises:
84+
ValueError: If the exchange rate is not available.
85+
"""
86+
if from_currency == self.__BASE_CURRENCY:
87+
return self.__rates.get(to_currency)
88+
elif to_currency == self.__BASE_CURRENCY:
89+
return 1 / self.__rates.get(from_currency)
90+
else:
91+
return self.__rates.get(to_currency) / self.__rates.get(from_currency)
92+
93+
def convert(self, amount, from_currency, to_currency, with_symbol=True):
94+
"""
95+
Convert an amount from one currency to another.
96+
97+
Args:
98+
amount (float): The amount of money to convert.
99+
from_currency (str): The currency to convert from.
100+
to_currency (str): The currency to convert to.
101+
with_symbol (bool): Whether to include the currency symbol in the result (default is `True`).
102+
103+
Returns:
104+
str: The converted amount formatted as a string, with or without the currency symbol.
105+
106+
Raises:
107+
ValueError: If the exchange rate for the currencies is not found.
108+
"""
109+
rate = self.__get_exchange_rate(from_currency, to_currency)
110+
if rate is None:
111+
raise ValueError("Invalid currency or rate not found")
112+
converted_amount = amount * rate
113+
return f'{converted_amount:.2f} {to_currency}' if not with_symbol else \
114+
f'{CURRENCY_SYMBOLS[to_currency]} {converted_amount:.2f}'

NumWord/humanize_number.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from utility import GetLanguage
2+
3+
4+
class HumanizeNumber:
5+
def __init__(self):
6+
self.suffixes = None
7+
8+
def convert(self, number, lang='en', to_lang=None):
9+
if to_lang is not None:
10+
return self.__convert_to_lang(number, lang, to_lang)
11+
else:
12+
self.__get_word(lang)
13+
for value, suffix in self.suffixes:
14+
if number >= 10 ** value:
15+
formatted_num = number / 10 ** value
16+
if formatted_num.is_integer():
17+
return f"{int(formatted_num)}{suffix}"
18+
formatted_str = f"{formatted_num:.2f}"
19+
if formatted_str[-1] == "0":
20+
formatted_str = f"{formatted_num:.1f}"
21+
return f"{formatted_str}{suffix}"
22+
23+
return str(number)
24+
25+
def __convert_to_lang(self, humanize_number, from_lang, to_lang):
26+
from_suffix = GetLanguage().get_language(from_lang)[3][::-1]
27+
28+
for value, suffix in from_suffix:
29+
if suffix in humanize_number:
30+
number = float(humanize_number.replace(suffix, "")) * 10 ** value
31+
return self.convert(number, to_lang)
32+
33+
return humanize_number
34+
35+
def __get_word(self, lang='en'):
36+
self.suffixes = GetLanguage().get_language(lang)[3][::-1]

README.md

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ NumWord is a Python package that converts numbers written in words to their nume
1515
- Convert numbers to words.
1616
- Supports English language.
1717
- Supports Hindi language.
18+
- Supports convert number into humanize number e.g.
19+
- 1500000 -> 1.5M
20+
- 1.5M -> 10L / 10 लाख
1821

1922
## Installation
2023

@@ -25,40 +28,67 @@ pip install -r requirements.txt
2528
```
2629

2730
## Usage
31+
2832
Here is an example of how to use the NumWord package:
2933

34+
### Convert number word into number
35+
3036
```python
31-
from NumWord import WordToNumber, NumberToWord
37+
from NumWord import WordToNumber
3238

33-
# Convert words to numbers in English
3439
word_to_num_converter = WordToNumber()
40+
41+
# Convert words to numbers in English
3542
result = word_to_num_converter.convert("one hundred twenty three point four five six")
3643
print(result) # Output: 123.456
3744

38-
# Convert numbers to words in English
39-
num_to_word_converter = NumberToWord()
40-
result = num_to_word_converter.convert(123.456)
41-
print(result) # Output: one hundred twenty-three point four five six
42-
43-
4445
# Convert words to numbers in Hindi
45-
word_to_num_converter = WordToNumber()
4646
result = word_to_num_converter.convert("एक सौ तेईस दशमलव चार पांच छह", lang='hi')
4747
print(result) # Output: 123.456
48+
```
49+
50+
### Convert number into number word
51+
52+
```python
53+
from NumWord import NumberToWord
4854

49-
# Convert numbers to words in Hindi
5055
num_to_word_converter = NumberToWord()
56+
57+
# Convert numbers to words in English
58+
result = num_to_word_converter.convert(123.456)
59+
print(result) # Output: one hundred twenty-three point four five six
60+
61+
# Convert numbers to words in Hindi
5162
result = num_to_word_converter.convert(123.456, lang='hi')
5263
print(result) # Output: एक सौ तेईस दशमलव चार पांच छह
53-
```
64+
```
5465

66+
### Convert number into Humanize Number or convert one number system to another humanize number system
67+
68+
```python
69+
from NumWord import HumanizeNumber
70+
71+
humanize_number = HumanizeNumber()
72+
73+
result = humanize_number.convert(1500000, lang='en')
74+
print(result) # Output: 1.5M
75+
76+
result = humanize_number.convert("1.5M", lang="en", to_lang="hi")
77+
print(result) # Output: 15 लाख
78+
79+
result = humanize_number.convert("1.5M", lang="en", to_lang="en-hi")
80+
print(result) # Output: 15L
81+
```
5582

5683
## Running Tests
84+
5785
To run the tests, use the following command:
86+
5887
```bash
5988
python -m unittest discover tests
6089
```
6190

6291
## License
92+
6393
This project is licensed under the MIT License - see the MIT License file for details.
6494

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[build-system]
2-
requires = ["setuptools>=42", "wheel"]
2+
requires = ["setuptools>=42", "wheel", "requests"]
33
build-backend = "setuptools.build_meta"

requirements.txt

72 Bytes
Binary file not shown.

setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='NumWord',
5-
version='0.0.1',
5+
version='0.0.3',
66
packages=find_packages(),
77
install_requires=[],
88
author='Harshit Dalal',
@@ -17,8 +17,8 @@
1717
'Operating System :: OS Independent',
1818
],
1919
keywords=[
20-
'number', 'word', 'conversion',
21-
'number to word', 'word to number'
20+
'number', 'word', 'conversion', 'numbers2words',
21+
'number to word', 'word to number', 'num2words', 'words2number'
2222
],
2323
python_requires='>=3.6',
24-
)
24+
)

tests/test_currency.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import unittest
2+
3+
from Logs import LoggerConfig
4+
from NumWord import Currency
5+
import os
6+
7+
8+
class TestCurrency(unittest.TestCase):
9+
10+
@classmethod
11+
def setUpClass(cls):
12+
cls.logger = LoggerConfig(__name__).get_logger()
13+
cls.logger.info("TestCurrency started.")
14+
cls.currency = Currency()
15+
16+
@classmethod
17+
def tearDownClass(cls):
18+
cls.logger.info("TestCurrency completed. \n -----------------")
19+
20+
def test_currency_conversion_valid(self):
21+
22+
result = self.currency.convert(100, "USD", "EUR", with_symbol=False)
23+
self.assertIsInstance(result, str)
24+
self.logger.info(f"Test Convert 100 USD to EUR without symbol -> {result}")
25+
self.assertTrue("EUR" in result)
26+
27+
result = self.currency.convert(50, "EUR", "INR", with_symbol=True)
28+
self.assertIsInstance(result, str)
29+
self.logger.info(f"Test Convert 50 EUR to INR with symbol -> {result}")
30+
self.assertTrue("₹" in result)
31+
32+
def test_invalid_currency(self):
33+
self.logger.info("Test Invalid Currency Code")
34+
with self.assertRaises(ValueError):
35+
self.currency.convert(100, "USD", "XYZ", with_symbol=True) # Invalid currency code
36+
37+
def test_load_exchange_rates_from_file(self):
38+
rates = self.currency._Currency__rates
39+
self.assertIsInstance(rates, dict)
40+
self.logger.info(f"Test Exchange rates load successfully: Total rates found -> {len(rates)}")
41+
self.assertGreater(len(rates), 0)
42+
43+
def test_fetch_exchange_rates(self):
44+
45+
data = self.currency._Currency__fetch_exchange_rates()
46+
self.logger.info(f"Test Base Code is USD -> {data['Base']}")
47+
self.assertEqual(data["Base"], "USD")
48+
self.assertGreater(len(data["rates"]), 0)
49+
50+
def test_currency_conversion_with_live_data(self):
51+
52+
self.logger.info(f"Test Cases with live exchange rates")
53+
result = self.currency.convert(100, "USD", "EUR", with_symbol=False)
54+
self.assertIsInstance(result, str)
55+
self.assertTrue("EUR" in result)
56+
57+
result = self.currency.convert(100, "USD", "EUR", with_symbol=True)
58+
self.assertIsInstance(result, str)
59+
self.assertTrue("€" in result)
60+
61+
62+
if __name__ == '__main__':
63+
unittest.main()

0 commit comments

Comments
 (0)