1
0
fin-defs/fin_defs/parse_price.py
Jon Michael Aanes 22aa122388
Some checks failed
Python Ruff Code Quality / ruff (push) Failing after 23s
Run Python tests (through Pytest) / Test (push) Failing after 24s
Verify Python project can be installed, loaded and have version checked / Test (push) Successful in 21s
Ruff
2025-05-14 21:01:50 +02:00

105 lines
2.8 KiB
Python

import logging
import re
from decimal import Decimal
from .data import (
CURRENCY_CODES,
CURRENCY_SYMBOLS,
Asset,
AssetAmount,
FiatCurrency,
)
logger = logging.getLogger(__name__)
RE_PRICE_RAW = r'\b(?:dkk|sek|usd|nok|eur)?\s*([1-9][\d.]*[\d](?:,\d+)?)\s*(?:,-|:-|.-|;-)?\s*(?:(?:kr|kroner|krone|dkk|sek|usd|eur|nok)\b)?\.?'
RE_PRICE = re.compile(RE_PRICE_RAW, flags=re.IGNORECASE)
RE_PRODUCT_PRICE_DK = r'-?\d{1,3}(?:\.?\d{3})*(?:,\d\d\.?)?'
RE_PRODUCT_PRICE_EN = r'-?\d{1,3}(?:,?\d{3})*(?:\.\d\d)?'
RE_PRODUCT_PRICE_AMOUNT = r'(' + RE_PRODUCT_PRICE_DK + '|' + RE_PRODUCT_PRICE_EN + ')'
def parse_amount(price: str) -> Decimal:
if re.fullmatch(RE_PRODUCT_PRICE_DK, price):
price = price.replace('.', '').replace(',', '.')
else:
price = price.replace(',', '')
return Decimal(price)
RE_CURRENCY_CODES = '(?P<code>' + '|'.join(re.escape(c) for c in CURRENCY_CODES) + ')'
RE_CURRENCY_SYMBOLS = (
r'(?P<sym>[' + ''.join(re.escape(c) for c in CURRENCY_SYMBOLS.values()) + '])'
)
RE_SYM_AMOUNT_CODE = re.compile(
RE_CURRENCY_SYMBOLS
+ r'\s*'
+ RE_PRODUCT_PRICE_AMOUNT
+ r'(?:\s+'
+ RE_CURRENCY_CODES
+ r')?',
flags=re.IGNORECASE,
)
RE_AMOUNT_SYM_CODE = re.compile(
RE_PRODUCT_PRICE_AMOUNT
+ r'\s*'
+ RE_CURRENCY_SYMBOLS
+ r'(?:\s+'
+ RE_CURRENCY_CODES
+ ')?',
flags=re.IGNORECASE,
)
RE_AMOUNT_CODE = re.compile(
RE_PRODUCT_PRICE_AMOUNT + r'\s+' + RE_CURRENCY_CODES,
flags=re.IGNORECASE,
)
RE_KR_AMOUNT = re.compile(
r'kr\.?\s*(' + RE_PRODUCT_PRICE_AMOUNT + ')',
flags=re.IGNORECASE,
)
RE_AMOUNT_KR = re.compile(
'(' + RE_PRODUCT_PRICE_AMOUNT + r')\s*kr\.?',
flags=re.IGNORECASE,
)
def parse_price(text: str, default_currency: Asset) -> AssetAmount | None:
"""
Attempts to parse price from the given text.
Text does not need to be stripped beforehand.
"""
if isinstance(text, AssetAmount):
return text
text = str(text).lower().strip()
if text == 'free':
return AssetAmount(default_currency, Decimal(0))
code, sym, amount_text = None, None, None
if m := RE_SYM_AMOUNT_CODE.fullmatch(text):
code, sym, amount_text = m.group('code'), m.group('sym'), m.group(2)
elif m := RE_AMOUNT_SYM_CODE.fullmatch(text):
code, sym, amount_text = m.group('code'), m.group('sym'), m.group(1)
elif m := RE_AMOUNT_CODE.fullmatch(text):
code, amount_text = m.group('code'), m.group(1)
elif m := (RE_KR_AMOUNT.fullmatch(text) or RE_AMOUNT_KR.fullmatch(text)):
code, amount_text = 'DKK', m.group(1)
else:
return None
currency = (
CURRENCY_CODES[code.upper()] if code else FiatCurrency.from_currency_symbol(sym)
)
assert currency is not None
return AssetAmount(currency, parse_amount(amount_text))