Skip to content

Commit

Permalink
- Update currencies.py to use a dictionary for all_currencies and pri…
Browse files Browse the repository at this point in the history
…ce_not_available_phrases.
  • Loading branch information
mohamedmamdouh22 committed Dec 6, 2024
1 parent 15c0b31 commit c5483c1
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 106 deletions.
119 changes: 53 additions & 66 deletions core/harambe_core/parser/currencies.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,57 @@
all_currencies = sorted(
[
("$", "USD"), # US Dollar
("€", "EUR"), # Euro
("¥", "JPY"), # Japanese Yen
("£", "GBP"), # British Pound
("A$", "AUD"), # Australian Dollar
("C$", "CAD"), # Canadian Dollar
("CHF", "CHF"), # Swiss Franc
("¥", "CNY"), # Chinese Yuan
("kr.", "SEK"), # Swedish Krona
("NZ$", "NZD"), # New Zealand Dollar
("S$", "SGD"), # Singapore Dollar
("HK$", "HKD"), # Hong Kong Dollar
("kr", "NOK"), # Norwegian Krone
("₩", "KRW"), # South Korean Won
("₺", "TRY"), # Turkish Lira
("₹", "INR"), # Indian Rupee
("₽", "RUB"), # Russian Ruble
("R$", "BRL"), # Brazilian Real
("R", "ZAR"), # South African Rand
("RM", "MYR"), # Malaysian Ringgit
("฿", "THB"), # Thai Baht
("Rp", "IDR"), # Indonesian Rupiah
("zł", "PLN"), # Polish Zloty
("₱", "PHP"), # Philippine Peso
("Kč", "CZK"), # Czech Koruna
("د.إ", "AED"), # UAE Dirham
("Ft", "HUF"), # Hungarian Forint
("₪", "ILS"), # Israeli Shekel
("﷼", "SAR"), # Saudi Riyal
("₦", "NGN"), # Nigerian Naira
("د.ك", "KWD"), # Kuwaiti Dinar
("B/.", "PAB"), # Panamanian Balboa
("kr", "DKK"), # Danish Krone
("S/.", "PEN"), # Peruvian Sol
("₲", "PYG"), # Paraguayan Guarani
("$", "MXN"), # Mexican Peso
("Q", "GTQ"), # Guatemalan Quetzal
("Bs.", "VEF"), # Venezuelan Bolivar
("$", "ARS"), # Argentine Peso
("$", "CLP"), # Chilean Peso
("$", "COP"), # Colombian Peso
("₡", "CRC"), # Costa Rican Colón
("د.م.", "MAD"), # Moroccan Dirham
("DH", "DZD"), # Algerian Dinar
("T", "KZT"), # Kazakhstani Tenge
("лв", "BGN"), # Bulgarian Lev
("L", "RON"), # Romanian Leu
("$", "TTD"), # Trinidad and Tobago Dollar
("ƒ", "ANG"), # Netherlands Antillean Guilder
("₭", "LAK"), # Lao Kip
("₮", "MNT"), # Mongolian Tugrik
("VT", "VUV"), # Vanuatu Vatu
("K", "PGK"), # Papua New Guinean Kina
("៛", "KHR"), # Cambodian Riel
("MK", "MWK"), # Malawian Kwacha
("MT", "MZN"), # Mozambican Metical
("Z$", "ZWL"), # Zimbabwean Dollar
("₫", "VND"), # Vietnamese Dong
("৳", "BDT"), # Bangladeshi Taka
],
key=lambda x: len(x[0]),
reverse=True,
)
from typing import Final

price_not_available_phrases = {
ALL_CURRENCIES: Final[dict[str, str]] = {
"د.م.": "MAD", # Moroccan Dirham
"د.إ": "AED", # UAE Dirham
"د.ك": "KWD", # Kuwaiti Dinar
"Bs.": "VEF", # Venezuelan Bolivar
"S/.": "PEN", # Peruvian Sol
"B/.": "PAB", # Panamanian Balboa
"NZ$": "NZD", # New Zealand Dollar
"HK$": "HKD", # Hong Kong Dollar
"A$": "AUD", # Australian Dollar
"C$": "CAD", # Canadian Dollar
"S$": "SGD", # Singapore Dollar
"R$": "BRL", # Brazilian Real
"Z$": "ZWL", # Zimbabwean Dollar
"kr.": "SEK", # Swedish Krona
"₽": "RUB", # Russian Ruble
"₺": "TRY", # Turkish Lira
"₩": "KRW", # South Korean Won
"₦": "NGN", # Nigerian Naira
"₫": "VND", # Vietnamese Dong
"₱": "PHP", # Philippine Peso
"₡": "CRC", # Costa Rican Colón
"₭": "LAK", # Lao Kip
"₮": "MNT", # Mongolian Tugrik
"₲": "PYG", # Paraguayan Guarani
"฿": "THB", # Thai Baht
"€": "EUR", # Euro
"₪": "ILS", # Israeli Shekel
"₹": "INR", # Indian Rupee
"৳": "BDT", # Bangladeshi Taka
"zł": "PLN", # Polish Zloty
"៛": "KHR", # Cambodian Riel
"kr": "NOK", # Norwegian Krone
"Kč": "CZK", # Czech Koruna
"RM": "MYR", # Malaysian Ringgit
"DH": "DZD", # Algerian Dinar
"Ft": "HUF", # Hungarian Forint
"MK": "MWK", # Malawian Kwacha
"MT": "MZN", # Mozambican Metical
"L": "RON", # Romanian Leu
"T": "KZT", # Kazakhstani Tenge
"VT": "VUV", # Vanuatu Vatu
"Q": "GTQ", # Guatemalan Quetzal
"R": "ZAR", # South African Rand
"£": "GBP", # British Pound
"¥": "JPY", # Japanese Yen
"ƒ": "ANG", # Netherlands Antillean Guilder
"$": "USD", # US Dollar
"CHF": "CHF", # Swiss Franc
}

PRICE_NOT_AVAILABLE_PHRASES: Final[set[str]] = {
"price not available",
"unavailable price",
"price upon request",
Expand Down
48 changes: 27 additions & 21 deletions core/harambe_core/parser/type_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,43 @@
from typing import Union, Optional
from typing_extensions import Annotated
from harambe_core.parser.type_price import ParserTypePrice
from harambe_core.parser.currencies import all_currencies, price_not_available_phrases
from harambe_core.parser.currencies import ALL_CURRENCIES, PRICE_NOT_AVAILABLE_PHRASES


class CurrencyParser:
@staticmethod
def parse_currency_amount(
value: Union[str, float, int, None],
) -> Union[float, None]:
return ParserTypePrice.validate_price(value)

def __call__(
self, value: Union[str, float, int, None]
) -> dict[str, Optional[Union[str, float]]]:
) -> Optional[dict[str, Optional[Union[str, float]]]]:
if isinstance(value, (float, int)):
return {"currency": None, "currency_symbol": None, "amount": float(value)}
symbol = currency_name = amount = None
if value is not None:
value = str(value).strip()
if value.lower() in price_not_available_phrases:
return {"currency": None, "currency_symbol": None, "amount": None}
for sym, curr in all_currencies:
if sym in value or curr.lower() in value.lower():
currency_name = curr
symbol = sym
break
value = value.replace(symbol, "").strip() if symbol else value
amount = self.parse_currency_amount(value)

if value is None or self._is_price_not_available(value):
return None

value_str = str(value).strip()
symbol, currency_name = self._identify_currency(value_str)
amount = self._extract_amount(value_str, symbol)

return {"currency": currency_name, "currency_symbol": symbol, "amount": amount}

@staticmethod
def _is_price_not_available(value: Union[str, float, int]) -> bool:
return str(value).strip().lower() in PRICE_NOT_AVAILABLE_PHRASES

@staticmethod
def _identify_currency(value: str) -> tuple[Optional[str], Optional[str]]:
for symbol, currency_name in ALL_CURRENCIES.items():
if symbol in value or currency_name.lower() in value.lower():
return symbol, currency_name
return None, None

@staticmethod
def _extract_amount(value: str, symbol: Optional[str]) -> Optional[float]:
if symbol:
value = value.replace(symbol, "").strip()
return ParserTypePrice.validate_price(value)


ParserTypeCurrency = Annotated[
dict[str, Optional[Union[str, float]]], BeforeValidator(CurrencyParser())
Optional[dict[str, Optional[Union[str, float]]]], BeforeValidator(CurrencyParser())
]
14 changes: 14 additions & 0 deletions core/test/parser/mock_schemas/currency.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/reworkd/harambe/refs/heads/main/schema.json",
"product": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "currency"
}
}
}
}
6 changes: 4 additions & 2 deletions core/test/parser/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ def test_no_data(data) -> None:
},
),
(
load_schema("enums"),
{"season": "spring"},
load_schema("currency"),
{
"product": {"name": "Product One", "price": None},
},
),
],
)
Expand Down
30 changes: 15 additions & 15 deletions core/test/parser/test_type_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,46 +81,46 @@ class _TestModel(BaseModel):
("B/. 123.45", {"currency": "PAB", "currency_symbol": "B/.", "amount": 123.45}),
(
"Price Not Available",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(
"Unavailable Price",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(
"Price Upon Request",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(
"Contact for Price",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(
"Request a Quote",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
("TDB", {"currency": None, "currency_symbol": None, "amount": None}),
("N/A", {"currency": None, "currency_symbol": None, "amount": None}),
("TDB", None),
("N/A", None),
(
"Price Not Disclosed",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
("Out of Stock", {"currency": None, "currency_symbol": None, "amount": None}),
("Sold Out", {"currency": None, "currency_symbol": None, "amount": None}),
("Out of Stock", None),
("Sold Out", None),
(
"Pricing Not Provided",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
("Not Priced", {"currency": None, "currency_symbol": None, "amount": None}),
("Not Priced", None),
(
"Currently Unavailable",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(
"Ask for Pricing",
{"currency": None, "currency_symbol": None, "amount": None},
None,
),
(None, {"currency": None, "currency_symbol": None, "amount": None}),
(None, None),
],
)
def test_currency_success(input_value, expected_output):
Expand Down
4 changes: 2 additions & 2 deletions core/test/parser/test_type_price.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class _TestModel(BaseModel):
("0.0004 $", 0.0004),
],
)
def test_flexible_price_success(input_value, expected_output):
def test_type_price_success(input_value, expected_output):
model = _TestModel(value=input_value)
assert model.value == pytest.approx(expected_output)

Expand All @@ -65,6 +65,6 @@ def test_flexible_price_success(input_value, expected_output):
[],
],
)
def test_flexible_float_failure(input_value):
def test_type_price_failure(input_value):
with pytest.raises(ValidationError):
_TestModel(value=input_value)

0 comments on commit c5483c1

Please sign in to comment.