Changeset - baa2e883cdfe
[Not reviewed]
0 2 0
Brett Smith - 7 years ago 2017-06-09 17:11:16
brettcsmith@brettcsmith.org
config: historical command accepts just two currency arguments.

This shows the rates between two currencies without converting a specific
amount.
2 files changed with 27 insertions and 19 deletions:
0 comments (0 inline, 0 general)
oxrlib/config.py
Show inline comments
...
 
@@ -102,59 +102,59 @@ class Configuration:
 
            '--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(
 
            '--signed-currency', '--sign-currency',
 
            type=currency_code, action='append', dest='signed_currencies',
 
            metavar='CODE',
 
            help="In Ledger output, use a sign for this currency if known. "
 
            "Can be specified multiple times.",
 
        )
 
        hist_parser.add_argument(
 
            'date',
 
            type=self._date_from_s,
 
            help="Use rates from this date, in YYYY-MM-DD format. "
 
            "If you omit the year or month, it fills in the current year/month."
 
        )
 
        hist_parser.add_argument(
 
            'word1', nargs='?', metavar='first code',
 
            help="Convert or show rates from this currency, in three-letter code format. "
 
            "If not specified, show all rates on the given date.",
 
            'word1', nargs='?', metavar='amount',
 
            help="Convert this amount of currency. If not specified, show rates.",
 
        )
 
        hist_parser.add_argument(
 
            'word2', nargs='?', metavar='amount',
 
            help="Convert this amount of currency. If not specified, show rates.",
 
            'word2', nargs='?', metavar='first code',
 
            help="Convert or show rates from this currency, in three-letter code format. "
 
            "If not specified, show all rates on the given date.",
 
        )
 
        hist_parser.add_argument(
 
            'word3', nargs='?', metavar='second code',
 
            help="Convert to this currency, in three-letter code format. "
 
            help="Convert or show rates to this currency, in three-letter code format. "
 
            "If not specified, defaults to the base currency.",
 
        )
 
        hist_parser.add_argument('word4', nargs='?', help=argparse.SUPPRESS)
 

	
 
        return prog_parser
 

	
 
    def _build_conffile(self):
 
        return configparser.ConfigParser()
 

	
 
    def _read_from_conffile(self, argname, sectionname, fallback, convert_to=None,
 
                            confname=None, getter='get', unset=None,
 
                            *, convert_fallback=False):
 
        if getattr(self.args, argname) is not unset:
 
            return
 
        elif confname is None:
 
            confname = argname
 
        get_method = getattr(self.conffile, getter)
 
        value = get_method(sectionname, confname, fallback=fallback)
 
        if (convert_to is not None
 
            and (value is not fallback or convert_fallback)):
 
            value = self._convert_or_error(convert_to, value, confname)
 
        setattr(self.args, argname, value)
 

	
 
    def _convert_or_error(self, argtype, s_value, argname=None, typename=None):
...
 
@@ -163,63 +163,67 @@ class Configuration:
 
        except (decimal.InvalidOperation, TypeError, ValueError):
 
            errmsg = []
 
            if argname:
 
                errmsg.append("argument {}".format(argname))
 
            if typename is None:
 
                typename = argtype.__name__.replace('_', ' ')
 
            errmsg.append("invalid {} value".format(typename))
 
            errmsg.append(repr(s_value))
 
            self.error(': '.join(errmsg))
 

	
 
    def _post_hook_historical(self):
 
        year = self.args.date.year
 
        if year < 100:
 
            # Don't let the user specify ambiguous dates.
 
            self.error("historical data not available from year {}".format(year))
 
        self._read_from_conffile('base', 'Historical', 'USD', currency_code)
 
        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')
 
        self.args.to_currency = self.args.base
 
        if self.args.word4 and (self.args.word3.lower() in self.PREPOSITIONS):
 
            self.args.word3 = self.args.word4
 
        if self.args.word1 is None:
 
        raw_words = iter(getattr(self.args, 'word' + c) for c in '1234')
 
        words = iter(word for word in raw_words if word is not None)
 
        try:
 
            next_word = next(words)
 
            try:
 
                self.args.amount = decimal.Decimal(next_word)
 
            except decimal.InvalidOperation:
 
                pass
 
        elif self.args.word2 is None:
 
            self.args.from_currency = self._convert_or_error(
 
                currency_code, self.args.word1)
 
            else:
 
            self.args.amount = self._convert_or_error(
 
                decimal.Decimal, self.args.word1)
 
            self.args.from_currency = self._convert_or_error(
 
                currency_code, self.args.word2)
 
            if self.args.word3 is not None:
 
                self.args.to_currency = self._convert_or_error(
 
                    currency_code, self.args.word3 or self.args.base)
 
                # If an amount was given, a currency code must be given too.
 
                # If it wasn't, set a value that can't be parsed as a currency.
 
                next_word = next(words, 'none given')
 
            self.args.from_currency = self._convert_or_error(currency_code, next_word)
 
            next_word = next(words)
 
            if next_word.lower() in self.PREPOSITIONS:
 
                next_word = next(words, next_word)
 
            self.args.to_currency = self._convert_or_error(currency_code, next_word)
 
        except StopIteration:
 
            pass
 

	
 
    def _build_cache_loader(self):
 
        kwargs = dict(self.conffile.items('Cache'))
 
        try:
 
            kwargs['dir_path'] = pathlib.Path(kwargs.pop('directory'))
 
        except KeyError:
 
            pass
 
        self.cache = cache.CacheWriter(**kwargs)
 
        return loaders.FileCache(**kwargs)
 

	
 
    def _build_oxrapi_loader(self):
 
        kwargs = dict(self.conffile.items('OXR'))
 
        return loaders.OXRAPIRequest(**kwargs)
 

	
 
    def get_loaders(self):
 
        loader_chain = loaders.LoaderChain()
 
        for build_func in [
 
                self._build_cache_loader,
 
                self._build_oxrapi_loader,
 
        ]:
 
            try:
 
                loader = build_func()
 
            except (TypeError, ValueError, configparser.NoSectionError):
 
                pass
tests/test_Configuration.py
Show inline comments
...
 
@@ -28,69 +28,73 @@ def test_full_config():
 

	
 
def test_incomplete_config():
 
    config = config_from('incomplete.ini')
 
    assert not config.get_loaders().loaders
 

	
 
def test_empty_config():
 
    config = config_from(os.devnull)
 
    assert not config.get_loaders().loaders
 

	
 
@pytest.mark.parametrize('ini_filename,expected_currency,use_switch', [
 
    (os.devnull, 'USD', False),
 
    ('full.ini', 'INI', False),
 
    ('full.ini', 'EUR', True),
 
])
 
def test_historical_default_base(ini_filename, expected_currency, use_switch, any_date):
 
    arglist = ['historical']
 
    if use_switch:
 
        arglist.extend(['--base', expected_currency])
 
    arglist.append(any_date.isoformat())
 
    config = config_from(ini_filename, arglist)
 
    assert config.args.base == expected_currency
 

	
 
@pytest.mark.parametrize('amount,from_curr,preposition,to_curr', [
 
    (None, 'JPY', None, None),
 
    (None, 'gbp', None, 'Aud'),
 
    (None, 'CHF', 'to', 'eur'),
 
    (decimal.Decimal('1000'), 'chf', None, None),
 
    (decimal.Decimal('999'), 'Eur', None, 'Chf'),
 
    (decimal.Decimal('12.34'), 'gbp', 'IN', 'eur'),
 
])
 
def test_historical_argparsing_success(amount, from_curr, preposition, to_curr, any_date):
 
    arglist = ['historical', any_date.isoformat()]
 
    arglist.extend(str(s) for s in [amount, from_curr, preposition, to_curr]
 
                   if s is not None)
 
    config = config_from(os.devnull, arglist)
 
    assert config.args.amount == amount
 
    assert config.args.from_currency == from_curr.upper()
 
    if to_curr is not None:
 
        assert config.args.to_currency == to_curr.upper()
 

	
 
@pytest.mark.parametrize('arglist', [
 
    ['100'],
 
    ['120', 'dollars'],
 
    ['to', '42', 'usd'],
 
    ['99', 'usd', 'minus', 'jpy'],
 
    ['usdjpy'],
 
    ['44', 'eur', 'in', 'chf', 'pronto'],
 
    ['eur', 'into'],
 
    ['50', 'jpy', 'in'],
 
])
 
def test_historical_argparsing_failure(arglist, any_date):
 
    arglist = ['historical', any_date.isoformat()] + arglist
 
    try:
 
        config = config_from(os.devnull, arglist)
 
    except SystemExit:
 
        pass
 
    else:
 
        assert not vars(config.args), "bad arglist succeeded"
 

	
 
@pytest.mark.parametrize('date_s,expect_year,expect_month,expect_day', [
 
    ('5', 1965, 4, 5),
 
    ('05', 1965, 4, 5),
 
    ('3-6', 1965, 3, 6),
 
    ('5.10', 1965, 5, 10),
 
    ('06-09', 1965, 6, 9),
 
    ('917/12/12', 917, 12, 12),
 
    ('2017-11-1', 2017, 11, 1),
 
])
 
def test_good_date_parsing(date_s, expect_year, expect_month, expect_day):
 
    oxrlib.config.Configuration.TODAY = datetime.date(1965, 4, 3)
 
    config = config_from(os.devnull, ['historical', date_s])
 
    actual_date = config.args.date
 
    assert actual_date.year == expect_year
0 comments (0 inline, 0 general)