first commit
This commit is contained in:
101
modules/currency/ecb.py
Normal file
101
modules/currency/ecb.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# 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))
|
||||
Reference in New Issue
Block a user