Changeset - ea0e3d3a73b7
[Not reviewed]
0 8 1
Brett Smith - 6 years ago 2018-04-03 15:21:12
brettcsmith@brettcsmith.org
dynload: Pre-configure objects and skip unconfigured ones.

Now that all importers and hooks receive the configuration object, they can
tell us at initialization time whether or not they actually have enough
configuration to be useful. Initialize them immediately on loading, and
only use importers and hooks that are actually configured.
9 files changed with 62 insertions and 16 deletions:
0 comments (0 inline, 0 general)
TODO.rst
Show inline comments
...
 
@@ -18,7 +18,6 @@ New importers
 
-------------
 

	
 
* Stripe import via API
 
  * This is going to require giving importer objects the configuration so they can read things like API keys.
 
* YourCause
 
* Network for Good
 

	
import2ledger/__main__.py
Show inline comments
...
 
@@ -11,8 +11,8 @@ logger = logging.getLogger('import2ledger')
 
class FileImporter:
 
    def __init__(self, config, stdout):
 
        self.config = config
 
        self.importers = [importer(config) for importer in importers.load_all()]
 
        self.hooks = [hook(config) for hook in hooks.load_all()]
 
        self.importers = importers.load_all(config)
 
        self.hooks = hooks.load_all(config)
 
        self.stdout = stdout
 

	
 
    def import_file(self, in_file, in_path=None):
import2ledger/dynload.py
Show inline comments
...
 
@@ -2,6 +2,8 @@ import importlib
 
import logging
 
import pathlib
 

	
 
from . import errors
 

	
 
logger = logging.getLogger('import2ledger')
 

	
 
def load_modules(src_dir_path):
...
 
@@ -23,8 +25,11 @@ def module_contents(module):
 
    for name in dir(module):
 
        yield name, getattr(module, name)
 

	
 
def submodule_items_named(file_path, name_test):
 
def submodule_items_named(file_path, name_test, config):
 
    for module in load_modules(pathlib.Path(file_path).parent):
 
        for name, item in module_contents(module):
 
            if name_test(name):
 
                yield item
 
                try:
 
                    yield item(config)
 
                except errors.NotConfiguredError as error:
 
                    logger.info("failed to load %s: %s", name, error.strerror)
import2ledger/errors.py
Show inline comments
...
 
@@ -12,6 +12,10 @@ class UserInputConfigurationError(UserInputError):
 
    pass
 

	
 

	
 
class NotConfiguredError(UserInputConfigurationError):
 
    pass
 

	
 

	
 
class UserInputFileError(UserInputError):
 
    def __init__(self, strerror, path):
 
        super().__init__(strerror, path)
import2ledger/hooks/__init__.py
Show inline comments
...
 
@@ -23,7 +23,8 @@ HOOK_KINDS = enum.Enum('HOOK_KINDS', [
 
    'OUTPUT',
 
])
 

	
 
def load_all():
 
    hooks = list(dynload.submodule_items_named(__file__, operator.methodcaller('endswith', 'Hook')))
 
def load_all(config):
 
    hooks = list(dynload.submodule_items_named(
 
        __file__, operator.methodcaller('endswith', 'Hook'), config))
 
    hooks.sort(key=operator.attrgetter('KIND.value'))
 
    return hooks
import2ledger/importers/__init__.py
Show inline comments
...
 
@@ -2,5 +2,6 @@ import operator
 

	
 
from .. import dynload
 

	
 
def load_all():
 
    return dynload.submodule_items_named(__file__, operator.methodcaller('endswith', 'Importer'))
 
def load_all(config):
 
    return list(dynload.submodule_items_named(
 
        __file__, operator.methodcaller('endswith', 'Importer'), config))
tests/date_hooks.py
Show inline comments
 
new file 100644
 
import datetime
 

	
 
class DateHookTestBase:
 
    DATE_FMT_ISO = '%Y-%m-%d'
 
    DATE_FMT_LEDGER = '%d/%m/%Y'
 

	
 
    def _format_config_date(self, section, key, value, date_fmt):
 
        if value is not None:
 
            section[key] = value.strftime(date_fmt)
 

	
 
    def new_config(
 
            self,
 
            fmt=DATE_FMT_ISO,
 
            default_date=datetime.date(2017, 3, 1),
 
            start_date=datetime.date(2017, 2, 1),
 
            end_date=datetime.date(2017, 4, 1)
 
    ):
 
        section = {'date format': fmt.replace('%', '%%')}
 
        self._format_config_date(section, 'default date', default_date, fmt)
 
        self._format_config_date(section, 'import start date', start_date, fmt)
 
        self._format_config_date(section, 'import end date', end_date, fmt)
 
        return {'Dates': section}
 

	
tests/test_hooks.py
Show inline comments
...
 
@@ -7,17 +7,28 @@ import pytest
 
from import2ledger import hooks
 
from import2ledger.hooks import add_entity, default_date, filter_by_date, ledger_entry
 

	
 
from . import Config
 
from . import date_hooks
 

	
 
def _run_order_test(config, expected_order):
 
    all_hooks = list(hooks.load_all(config))
 
    positions = {type(hook): index for index, hook in enumerate(all_hooks)}
 
    actual_order = list(sorted(expected_order, key=positions.__getitem__))
 
    assert actual_order == expected_order
 

	
 
def test_load_no_config_needed():
 
    return _run_order_test(Config(), [
 
        add_entity.AddEntityHook,
 
    ])
 

	
 
def test_load_all():
 
    all_hooks = list(hooks.load_all())
 
    positions = {hook: index for index, hook in enumerate(all_hooks)}
 
    expected_order = [
 
    config_dict = date_hooks.DateHookTestBase().new_config()
 
    return _run_order_test(Config(config_dict), [
 
        default_date.DefaultDateHook,
 
        add_entity.AddEntityHook,
 
        filter_by_date.FilterByDateHook,
 
        ledger_entry.LedgerEntryHook,
 
    ]
 
    actual_order = list(sorted(expected_order, key=positions.__getitem__))
 
    assert actual_order == expected_order
 
    ])
 

	
 
class DateRangeConfig:
 
    def __init__(self, start_date=None, end_date=None):
tests/test_importers.py
Show inline comments
...
 
@@ -12,6 +12,8 @@ from import2ledger import errors, importers, strparse
 
from . import Config, DATA_DIR
 

	
 
class TestImporters:
 
    FULL_CONFIG = Config({})
 

	
 
    with pathlib.Path(DATA_DIR, 'imports.yml').open() as yaml_file:
 
        test_data = yaml.load(yaml_file)
 
    for test in test_data:
...
 
@@ -46,6 +48,6 @@ class TestImporters:
 
                assert actual == expected
 

	
 
    def test_loader(self):
 
        all_importers = list(importers.load_all())
 
        all_importers = {type(importer) for importer in importers.load_all(self.FULL_CONFIG)}
 
        for test in self.test_data:
 
            assert test['importer'] in all_importers
0 comments (0 inline, 0 general)