Changeset - 55f5833aa071
[Not reviewed]
0 5 0
Brett Smith - 7 years ago 2017-06-09 15:06:51
brettcsmith@brettcsmith.org
historical: Add a setting to denominate Ledger conversions.

This makes conversion output easier to add to ledgers directly.
5 files changed with 93 insertions and 20 deletions:
0 comments (0 inline, 0 general)
oxrlib/commands/historical.py
Show inline comments
...
 
@@ -57,10 +57,12 @@ class Formatter:
 
class LedgerFormatter(Formatter):
 
    RATE_PREC = 5
 
    def __init__(self, rate, signed_currencies=(), base_fmt='#,##0.###',
 
                 rate_precision=5, denomination=None):
 
        super().__init__(rate, signed_currencies, base_fmt)
 
        self.rate_prec = rate_precision
 
        self.denomination = denomination
 

	
 
    def normalize_rate(self, rate, prec=None):
 
        # Return prec nonzero digits of precision, if available.
 
        if prec is None:
 
            prec = self.RATE_PREC
 
    def normalize_rate(self, rate):
 
        _, digits, exponent = rate.normalize().as_tuple()
 
        prec -= min(0, exponent + len(digits))
 
        # Return ``self.rate_prec`` nonzero digits of precision, if available.
 
        prec = self.rate_prec - min(0, exponent + len(digits))
 
        quant_to = '1.{}'.format('0' * prec)
...
 
@@ -73,7 +75,7 @@ class LedgerFormatter(Formatter):
 

	
 
    def format_rate(self, rate, prec=None):
 
        return str(self.normalize_rate(rate, prec))
 
    def format_rate(self, rate):
 
        return str(self.normalize_rate(rate))
 

	
 
    def format_ledger_rate(self, rate, curr, prec=None):
 
        nrate = self.normalize_rate(rate, prec)
 
    def format_ledger_rate(self, rate, curr):
 
        nrate = self.normalize_rate(rate)
 
        rate_s = self.format_currency(nrate, curr, currency_digits=False)
...
 
@@ -87,9 +89,24 @@ class LedgerFormatter(Formatter):
 

	
 
    def _denomination_for(self, currency, default):
 
        if self.denomination is None:
 
            return default
 
        elif self.denomination == currency:
 
            return None
 
        else:
 
            return self.denomination
 

	
 
    def format_denominated_rate(self, amount, currency, default_denomination):
 
        denomination = self._denomination_for(currency, default_denomination)
 
        amt_s = self.format_currency(amount, currency)
 
        if denomination is None:
 
            return amt_s
 
        else:
 
            rate = self.rate.convert(1, currency, denomination)
 
            return "{} {}".format(amt_s, self.format_ledger_rate(rate, denomination))
 

	
 
    def format_conversion(self, from_amt, from_curr, to_curr):
 
        to_rate = self.rate.convert(1, from_curr, to_curr)
 
        to_amt = self.rate.convert(from_amt, from_curr, to_curr)
 
        return "{} {}\n{}".format(
 
            self.format_currency(from_amt, from_curr),
 
            self.format_ledger_rate(to_rate, to_curr),
 
            self.format_currency(to_amt, to_curr),
 
        return "{}\n{}".format(
 
            self.format_denominated_rate(from_amt, from_curr, to_curr),
 
            self.format_denominated_rate(to_amt, to_curr, None),
 
        )
...
 
@@ -104,3 +121,4 @@ def run(config, stdout, stderr):
 
    if config.args.ledger:
 
        formatter = LedgerFormatter(rate, config.args.signed_currencies)
 
        formatter = LedgerFormatter(rate, config.args.signed_currencies,
 
                                    denomination=config.args.denomination)
 
    else:
oxrlib/config.py
Show inline comments
...
 
@@ -22,2 +22,3 @@ class Configuration:
 
    DEFAULT_CONFIG_PATH = pathlib.Path(HOME_PATH, '.config', 'oxrlib.ini')
 
    NO_DENOMINATION = object()
 
    PREPOSITIONS = frozenset(['in', 'to', 'into'])
...
 
@@ -99,2 +100,13 @@ class Configuration:
 
        )
 
        hist_parser.add_argument(
 
            '--denomination',
 
            metavar='CODE', type=currency_code,
 
            help="In Ledger conversion output, always show rates to convert "
 
            "to this currency",
 
        )
 
        hist_parser.add_argument(
 
            '--no-denomination',
 
            dest='denomination', action='store_const', const=self.NO_DENOMINATION,
 
            help="Turn off an earlier --denomination setting",
 
        )
 
        hist_parser.add_argument(
...
 
@@ -134,3 +146,4 @@ class Configuration:
 
    def _read_from_conffile(self, argname, sectionname, fallback, convert_to=None,
 
                            confname=None, getter='get', unset=None):
 
                            confname=None, getter='get', unset=None,
 
                            *, convert_fallback=False):
 
        if getattr(self.args, argname) is not unset:
...
 
@@ -141,3 +154,4 @@ class Configuration:
 
        value = get_method(sectionname, confname, fallback=fallback)
 
        if convert_to is not None:
 
        if (convert_to is not None
 
            and (value is not fallback or convert_fallback)):
 
            value = self._convert_or_error(convert_to, value, confname)
...
 
@@ -164,3 +178,8 @@ class Configuration:
 
        self._read_from_conffile('base', 'Historical', 'USD', currency_code)
 
        self._read_from_conffile('signed_currencies', 'Historical', self.args.base, currency_list)
 
        if self.args.denomination is self.NO_DENOMINATION:
 
            self.args.denomination = None
 
        else:
 
            self._read_from_conffile('denomination', 'Historical', None, currency_code)
 
        self._read_from_conffile('signed_currencies', 'Historical', self.args.base,
 
                                 currency_list, convert_fallback=True)
 
        self._read_from_conffile('ledger', 'Historical', False, getter='getboolean')
oxrlib_example.ini
Show inline comments
...
 
@@ -29,2 +29,9 @@ ledger = no
 

	
 
# Denominate Ledger books in this currency.
 
# Ledger-formatted conversions will always show a rate to convert to this
 
# currency, even when converting between two other currencies.
 
# If not specified, output will show the rate for the currency you're
 
# converting to.
 
denomination = USD
 

	
 
# Use signs for these currencies in Ledger output.
setup.py
Show inline comments
...
 
@@ -7,3 +7,3 @@ setup(
 
    description="Library to query the Open Exchange Rates (OXR) API",
 
    version='1.3',
 
    version='1.4',
 
    author='Brett Smith',
tests/test_historical.py
Show inline comments
...
 
@@ -51,2 +51,3 @@ def build_config(
 
        signed_currencies=None,
 
        denomination=None,
 
        base='USD',
...
 
@@ -61,2 +62,3 @@ def build_config(
 
        'signed_currencies': [base] if signed_currencies is None else signed_currencies,
 
        'denomination': denomination,
 
    })
...
 
@@ -119 +121,28 @@ def test_signed_currencies(historical1_responder, output):
 
    assert next(lines, None) is None
 

	
 
def test_denomination(historical1_responder, output):
 
    config = build_config(historical1_responder, from_currency='ANG',
 
                          to_currency='AED', amount=10,
 
                          ledger=True, denomination='USD')
 
    lines = lines_from_run(config, output)
 
    assert next(lines) == '10.00 ANG {=$0.55866} @ $0.55866\n'
 
    assert next(lines) == '20.52 AED {=$0.2723} @ $0.2723\n'
 
    assert next(lines, None) is None
 

	
 
def test_redundant_denomination(historical1_responder, output):
 
    config = build_config(historical1_responder, from_currency='ANG',
 
                          to_currency='USD', amount=10,
 
                          ledger=True, denomination='USD')
 
    lines = lines_from_run(config, output)
 
    assert next(lines) == '10.00 ANG {=$0.55866} @ $0.55866\n'
 
    assert next(lines) == '$5.59\n'
 
    assert next(lines, None) is None
 

	
 
def test_from_denomination(historical1_responder, output):
 
    config = build_config(historical1_responder, from_currency='USD',
 
                          to_currency='ALL', amount=10,
 
                          ledger=True, denomination='USD')
 
    lines = lines_from_run(config, output)
 
    assert next(lines) == '$10.00\n'
 
    assert next(lines) == '1,445 ALL {=$0.006919} @ $0.006919\n'
 
    assert next(lines, None) is None
0 comments (0 inline, 0 general)