Changeset - 0120e4ce5a5a
[Not reviewed]
0 0 4
Brett Smith - 7 years ago 2017-05-16 13:19:37
brettcsmith@brettcsmith.org
config: Begin Configuration class.
4 files changed with 167 insertions and 0 deletions:
0 comments (0 inline, 0 general)
oxrlib/config.py
Show inline comments
 
new file 100644
 
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
tests/config_ini/full.ini
Show inline comments
 
new file 100644
 
[Cache]
 
directory = /tmp
 
historical = {date}_{base}_rates.json
 

	
 
[OXR]
 
app_id = 1234567890abcdef1234567890abcdef
 
api_root = http://[100::]/oxrlibtest/
 

	
 
[Historical]
 
base = INI
tests/config_ini/incomplete.ini
Show inline comments
 
new file 100644
 
[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/
tests/test_Configuration.py
Show inline comments
 
new file 100644
 
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
0 comments (0 inline, 0 general)