oxrlib/oxrlib/config.py

162 lines
5.6 KiB
Python
Raw Normal View History

2017-05-16 12:45:12 +00:00
import argparse
import configparser
import datetime
import decimal
2017-05-16 12:45:12 +00:00
import os.path
import pathlib
2017-05-17 16:29:50 +00:00
from . import cache, loaders
2017-05-16 12:45:12 +00:00
HOME_PATH = pathlib.Path(os.path.expanduser('~'))
CONFFILE_SEED = """
[Historical]
base=USD
"""
def currency_code(s):
if not ((len(s) == 3) and s.isalpha()):
2017-05-16 12:45:12 +00:00
raise ValueError("bad currency code: {!r}".format(s))
return s.upper()
def date_from(fmt_s):
def date_from_fmt(s):
return datetime.datetime.strptime(s, fmt_s).date()
2017-05-16 12:45:12 +00:00
return date_from_fmt
class Configuration:
DEFAULT_CONFIG_PATH = pathlib.Path(HOME_PATH, '.config', 'oxrlib.ini')
PREPOSITIONS = frozenset(['in', 'to', 'into'])
2017-05-16 12:45:12 +00:00
def __init__(self, arglist):
argparser = self._build_argparser()
self.error = argparser.error
self.args = argparser.parse_args(arglist)
if self.args.config_file is None:
self.args.config_file = [self.DEFAULT_CONFIG_PATH]
self.conffile = self._build_conffile()
conffile_paths = [path.as_posix() for path in self.args.config_file]
read_files = self.conffile.read(conffile_paths)
for expected_path, read_path in zip(conffile_paths, read_files):
if read_path != expected_path:
self.error("failed to read configuration file {!r}".format(expected_path))
try:
post_hook = getattr(self, '_post_hook_' + self.args.command)
except AttributeError:
pass
else:
post_hook()
def _build_argparser(self):
prog_parser = argparse.ArgumentParser()
prog_parser.add_argument(
'--config-file', '-c',
action='append', type=pathlib.Path,
help="Path of a configuration file to read",
)
subparsers = prog_parser.add_subparsers()
hist_parser = subparsers.add_parser(
'historical', aliases=['hist'],
usage='%(prog)s historical YYYY-MM-DD [[amount] code] [[in] code]',
help="Show a currency conversion or rate from a past date",
)
hist_parser.set_defaults(
command='historical',
amount=None,
from_currency=None,
)
2017-05-16 12:45:12 +00:00
hist_parser.add_argument(
'--base',
type=currency_code,
2017-05-16 12:45:12 +00:00
help="Base currency (default USD)",
)
hist_parser.add_argument(
'date',
type=date_from('%Y-%m-%d'),
help="Use rates from this date, in YYYY-MM-DD format"
)
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.",
2017-05-16 12:45:12 +00:00
)
hist_parser.add_argument(
'word2', nargs='?', metavar='amount',
help="Convert this amount of currency. If not specified, show rates.",
)
hist_parser.add_argument(
'word3', nargs='?', metavar='second code',
help="Convert 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)
2017-05-16 12:45:12 +00:00
return prog_parser
def _build_conffile(self):
conffile = configparser.ConfigParser()
conffile.read_string(CONFFILE_SEED)
return conffile
def _convert_or_error(self, argtype, s_value, argname=None, typename=None):
try:
return argtype(s_value)
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))
2017-05-16 12:45:12 +00:00
def _post_hook_historical(self):
if self.args.base is None:
self.args.base = self.conffile.get('Historical', 'base')
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:
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)
2017-05-16 12:45:12 +00:00
def _build_cache_loader(self):
kwargs = dict(self.conffile.items('Cache'))
try:
kwargs['dir_path'] = pathlib.Path(kwargs.pop('directory'))
2017-05-16 12:45:12 +00:00
except KeyError:
pass
2017-05-17 16:29:50 +00:00
self.cache = cache.CacheWriter(**kwargs)
2017-05-16 12:45:12 +00:00
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
else:
loader_chain.add_loader(loader)
return loader_chain