File: ecb.py

package info (click to toggle)
tryton-modules-currency 7.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 636 kB
  • sloc: python: 1,175; xml: 234; makefile: 11; sh: 3
file content (101 lines) | stat: -rw-r--r-- 3,150 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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))