Files @ ae3e4617d31e
Branch filter:

Location: NPO-Accounting/oxrlib/oxrlib/rate.py

Brett Smith
historical: Always format rates with the same precision.

When we format a rate as a price, we don't know how much precision
is "enough" to do the conversion, because we don't know what's
being converted to. As a result, we may (=will almost certainly)
end up formatting the rate with different precision on the cost
date vs. the price date, and that causes Beancount/Ledger to fail
to make the connection between them.

Using a constant of 6 is enough to make the current test for
"enough" precision pass, so just do that for now. This might need
further refinement in the future.
import datetime
import decimal
import functools
import json

class Rate:
    FIELDS = [
        'base',
        'disclaimer',
        'license',
        'rates',
        'timestamp',
    ]

    @classmethod
    def walk_fields(cls, get_field, transform_prefix):
        for fieldname in cls.FIELDS:
            value = get_field(fieldname)
            if value is not None:
                try:
                    transformer = getattr(cls, '{}_{}'.format(transform_prefix, fieldname))
                except AttributeError:
                    pass
                else:
                    value = transformer(value)
            yield (fieldname, value)

    def __init__(self, base, rates, **kwargs):
        for key, value in self.walk_fields(kwargs.get, 'deserialize'):
            setattr(self, key, value)
        self.base = base
        self.rates = rates

    @classmethod
    def deserialize_timestamp(cls, value):
        return datetime.datetime.fromtimestamp(value)

    @classmethod
    def serialize_rates(cls, rates):
        return {code: float(rates[code]) for code in rates}

    @classmethod
    def serialize_timestamp(cls, value):
        return int(value.timestamp())

    @classmethod
    def from_json_file(cls, json_file):
        response = json.load(json_file, parse_int=decimal.Decimal, parse_float=decimal.Decimal)
        return cls(**response)

    def convert(self, amount, from_currency, to_currency):
        return amount * self.rates[to_currency] / self.rates[from_currency]

    def serialize(self):
        return dict(self.walk_fields(functools.partial(getattr, self), 'serialize'))