config: Begin Configuration class.
This commit is contained in:
parent
6a416b162d
commit
0120e4ce5a
4 changed files with 167 additions and 0 deletions
104
oxrlib/config.py
Normal file
104
oxrlib/config.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
import argparse
|
||||
import configparser
|
||||
import datetime
|
||||
import os.path
|
||||
import pathlib
|
||||
|
||||
from . import loaders
|
||||
|
||||
HOME_PATH = pathlib.Path(os.path.expanduser('~'))
|
||||
CONFFILE_SEED = """
|
||||
[Historical]
|
||||
base=USD
|
||||
"""
|
||||
|
||||
def currency_code(s):
|
||||
if not (len(s) == 3) and s.isalpha():
|
||||
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
|
||||
return date_from_fmt
|
||||
|
||||
class Configuration:
|
||||
DEFAULT_CONFIG_PATH = pathlib.Path(HOME_PATH, '.config', 'oxrlib.ini')
|
||||
|
||||
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'])
|
||||
hist_parser.set_defaults(command='historical')
|
||||
hist_parser.add_argument(
|
||||
'--base',
|
||||
help="Base currency (default USD)",
|
||||
)
|
||||
hist_parser.add_argument(
|
||||
'date',
|
||||
type=date_from('%Y-%m-%d'), metavar='YYYY-MM-DD',
|
||||
)
|
||||
|
||||
return prog_parser
|
||||
|
||||
def _build_conffile(self):
|
||||
conffile = configparser.ConfigParser()
|
||||
conffile.read_string(CONFFILE_SEED)
|
||||
return conffile
|
||||
|
||||
def _post_hook_historical(self):
|
||||
if self.args.base is None:
|
||||
self.args.base = self.conffile.get('Historical', 'base')
|
||||
|
||||
def _build_cache_loader(self):
|
||||
kwargs = dict(self.conffile.items('Cache'))
|
||||
try:
|
||||
kwargs['dir_path'] = kwargs.pop('directory')
|
||||
except KeyError:
|
||||
pass
|
||||
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
|
10
tests/config_ini/full.ini
Normal file
10
tests/config_ini/full.ini
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Cache]
|
||||
directory = /tmp
|
||||
historical = {date}_{base}_rates.json
|
||||
|
||||
[OXR]
|
||||
app_id = 1234567890abcdef1234567890abcdef
|
||||
api_root = http://[100::]/oxrlibtest/
|
||||
|
||||
[Historical]
|
||||
base = INI
|
9
tests/config_ini/incomplete.ini
Normal file
9
tests/config_ini/incomplete.ini
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Cache]
|
||||
# No directory.
|
||||
# FIXME: Write pattern-validating code, and then make this the section:
|
||||
# directory = /tmp
|
||||
# historical = rates.json
|
||||
|
||||
[OXR]
|
||||
# No app_id.
|
||||
api_root = http://[100::]/oxrlibtest/
|
44
tests/test_Configuration.py
Normal file
44
tests/test_Configuration.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from . import any_date, relpath
|
||||
|
||||
import oxrlib.config
|
||||
import oxrlib.loaders
|
||||
|
||||
INI_DIR_PATH = relpath('config_ini')
|
||||
|
||||
def config_from(ini_filename, arglist=None):
|
||||
if arglist is None:
|
||||
arglist = ['historical', any_date().isoformat()]
|
||||
ini_path = INI_DIR_PATH / ini_filename
|
||||
return oxrlib.config.Configuration(['--config-file', ini_path.as_posix()] + arglist)
|
||||
|
||||
def test_full_config():
|
||||
config = config_from('full.ini')
|
||||
loaders = config.get_loaders().loaders
|
||||
assert type(loaders[0]) is oxrlib.loaders.FileCache
|
||||
assert type(loaders[1]) is oxrlib.loaders.OXRAPIRequest
|
||||
assert len(loaders) == 2
|
||||
|
||||
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
|
Loading…
Reference in a new issue