102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
# this repository contains the full copyright notices and license terms.
|
|
|
|
import datetime as dt
|
|
import ssl
|
|
import sys
|
|
|
|
try:
|
|
from lxml import etree as ET
|
|
except ImportError:
|
|
try:
|
|
import xml.etree.cElementTree as ET
|
|
except ImportError:
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from decimal import Decimal
|
|
from urllib.error import HTTPError
|
|
from urllib.request import urlopen
|
|
|
|
_URL = 'https://www.ecb.europa.eu/stats/eurofxref/'
|
|
_URL_TODAY = _URL + 'eurofxref-daily.xml'
|
|
_URL_90 = _URL + 'eurofxref-hist-90d.xml'
|
|
_URL_HIST = _URL + 'eurofxref-hist.xml'
|
|
_START_DATE = dt.date(1999, 1, 4)
|
|
_CUBE_TAG = '{http://www.ecb.int/vocabulary/2002-08-01/eurofxref}Cube'
|
|
|
|
|
|
class RatesNotAvailableError(Exception):
|
|
pass
|
|
|
|
|
|
class UnsupportedCurrencyError(Exception):
|
|
pass
|
|
|
|
|
|
def _parse_time(time):
|
|
return dt.datetime.strptime(time, '%Y-%m-%d').date()
|
|
|
|
|
|
def _find_time(source, time=None):
|
|
for _, element in ET.iterparse(source):
|
|
if element.tag == _CUBE_TAG and 'time' in element.attrib:
|
|
if time and _parse_time(element.attrib.get('time')) <= time:
|
|
return element
|
|
elif time is None:
|
|
return element
|
|
element.clear()
|
|
|
|
|
|
def get_rates(currency='EUR', date=None):
|
|
if date is None:
|
|
date = dt.date.today()
|
|
if date < _START_DATE:
|
|
date = _START_DATE
|
|
context = ssl.create_default_context()
|
|
try:
|
|
with urlopen(_URL_TODAY, context=context) as response:
|
|
element = _find_time(response)
|
|
last_date = _parse_time(element.attrib['time'])
|
|
if last_date < date:
|
|
raise RatesNotAvailableError()
|
|
elif last_date == date:
|
|
return _compute_rates(element, currency, date)
|
|
|
|
if last_date - date < dt.timedelta(days=90):
|
|
url = _URL_90
|
|
else:
|
|
url = _URL_HIST
|
|
with urlopen(url, context=context) as response:
|
|
element = _find_time(response, date)
|
|
if element is None and url == _URL_90:
|
|
with urlopen(_URL_HIST, context=context) as response:
|
|
element = _find_time(response, date)
|
|
return _compute_rates(element, currency, date)
|
|
except HTTPError as e:
|
|
raise RatesNotAvailableError() from e
|
|
|
|
|
|
def _compute_rates(element, currency, date):
|
|
currencies = {}
|
|
for cur in element:
|
|
currencies[cur.attrib['currency']] = Decimal(cur.attrib['rate'])
|
|
if currency != 'EUR':
|
|
currencies['EUR'] = Decimal(1)
|
|
try:
|
|
base_rate = currencies.pop(currency)
|
|
except KeyError:
|
|
raise UnsupportedCurrencyError(f'{currency} is not available')
|
|
for cur, rate in currencies.items():
|
|
currencies[cur] = (rate / base_rate).quantize(Decimal('.0001'))
|
|
return currencies
|
|
|
|
|
|
if __name__ == '__main__':
|
|
currency = 'EUR'
|
|
if len(sys.argv) > 1:
|
|
currency = sys.argv[1]
|
|
date = None
|
|
if len(sys.argv) > 2:
|
|
date = dt.datetime.strptime(sys.argv[2], '%Y-%m-%d').date()
|
|
print(get_rates(currency, date))
|