From 0d370c445b9e6cbfed05a64b9cd844dc254a3f0c 2020-03-19 21:23:27 From: Brett Smith Date: 2020-03-19 21:23:27 Subject: [PATCH] plugin: User configuration is passed to hooks on initialization. --- diff --git a/conservancy_beancount/errors.py b/conservancy_beancount/errors.py index e1eb348578e0e25456f1308fad90d56c6506f230..074fc1d945c670836b4816080e8ebb3c52a01a92 100644 --- a/conservancy_beancount/errors.py +++ b/conservancy_beancount/errors.py @@ -31,9 +31,21 @@ class Error(Exception): source=self.source, ) + def _fill_source(self, source, filename='conservancy_beancount', lineno=0): + source.setdefault('filename', filename) + source.setdefault('lineno', lineno) + Iter = Iterable[Error] +class ConfigurationError(Error): + def __init__(self, message, entry=None, source=None): + if source is None: + source = {} + self._fill_source(source) + super().__init__(message, entry, source) + + class InvalidMetadataError(Error): def __init__(self, txn, post, key, value=None, source=None): if value is None: diff --git a/conservancy_beancount/plugin/__init__.py b/conservancy_beancount/plugin/__init__.py index 7a79f192563a3fff293d10a4ac9d844c82712613..1730c8d0a4a8005ad0a02f36e5be30f955c3c8c0 100644 --- a/conservancy_beancount/plugin/__init__.py +++ b/conservancy_beancount/plugin/__init__.py @@ -32,6 +32,7 @@ from ..beancount_types import ( ALL_DIRECTIVES, Directive, ) +from .. import config as configmod from .core import ( Hook, HookName, @@ -100,8 +101,14 @@ def run( ) -> Tuple[List[Directive], List[Error]]: errors: List[Error] = [] hooks: Dict[HookName, List[Hook]] = {} + user_config = configmod.Config() for key, hook_type in hook_registry.group_by_directive(config): - hooks.setdefault(key, []).append(hook_type()) + try: + hook = hook_type(user_config) + except Error as error: + errors.append(error) + else: + hooks.setdefault(key, []).append(hook) for entry in entries: entry_type = type(entry).__name__ for hook in hooks[entry_type]: diff --git a/conservancy_beancount/plugin/core.py b/conservancy_beancount/plugin/core.py index f2fb69bd6d62399c4e5de54f43ffbf0ceff7afc0..6699da99b7d8b13944a4e04ebf0b4e3e93b22245 100644 --- a/conservancy_beancount/plugin/core.py +++ b/conservancy_beancount/plugin/core.py @@ -18,6 +18,7 @@ import abc import datetime import re +from .. import config as configmod from .. import data from .. import errors as errormod @@ -60,6 +61,11 @@ class Hook(Generic[Entry], metaclass=abc.ABCMeta): DIRECTIVE: Type[Directive] HOOK_GROUPS: FrozenSet[HookName] = frozenset() + def __init__(self, config: configmod.Config) -> None: + pass + # Subclasses that need configuration should override __init__ to check + # and store it. + @abc.abstractmethod def run(self, entry: Entry) -> errormod.Iter: ... diff --git a/tests/test_meta_expense_allocation.py b/tests/test_meta_expense_allocation.py index 408f4b7973d6b074b628ebbecbe3455e17516440..c96f88e4dc6c390358b4237a9bb106d95938c884 100644 --- a/tests/test_meta_expense_allocation.py +++ b/tests/test_meta_expense_allocation.py @@ -39,7 +39,8 @@ TEST_KEY = 'expense-allocation' @pytest.fixture(scope='module') def hook(): - return meta_expense_allocation.MetaExpenseAllocation() + config = testutil.TestConfig() + return meta_expense_allocation.MetaExpenseAllocation(config) @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) def test_valid_values_on_postings(hook, src_value, set_value): diff --git a/tests/test_meta_income_type.py b/tests/test_meta_income_type.py index 7003758ad83b5ddf3f2ecb1bef8409e4b3dfb52b..9adf4255305be9ec95e9d2584d4d13fd531f0489 100644 --- a/tests/test_meta_income_type.py +++ b/tests/test_meta_income_type.py @@ -39,7 +39,8 @@ TEST_KEY = 'income-type' @pytest.fixture(scope='module') def hook(): - return meta_income_type.MetaIncomeType() + config = testutil.TestConfig() + return meta_income_type.MetaIncomeType(config) @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) def test_valid_values_on_postings(hook, src_value, set_value): diff --git a/tests/test_meta_tax_implication.py b/tests/test_meta_tax_implication.py index c01054ca86e0858a4ef3361db64d24bcf05c263b..2e98dcb623074b19c381b12c5e79140e2b87f242 100644 --- a/tests/test_meta_tax_implication.py +++ b/tests/test_meta_tax_implication.py @@ -51,7 +51,8 @@ TEST_KEY = 'tax-implication' @pytest.fixture(scope='module') def hook(): - return meta_tax_implication.MetaTaxImplication() + config = testutil.TestConfig() + return meta_tax_implication.MetaTaxImplication(config) @pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) def test_valid_values_on_postings(hook, src_value, set_value): diff --git a/tests/test_plugin.py b/tests/test_plugin.py index c2aba1bff4b3c2b483debb34b131d7b5a599491a..f64d1b9b6ddd153ed940e9201f11ab9d4026ec87 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -18,24 +18,39 @@ import pytest from . import testutil -from conservancy_beancount import beancount_types, plugin +from conservancy_beancount import beancount_types, errors as errormod, plugin HOOK_REGISTRY = plugin.HookRegistry() +class NonError(errormod.Error): + pass + + class TransactionHook: DIRECTIVE = beancount_types.Transaction HOOK_GROUPS = frozenset() + def __init__(self, config): + self.config = config + def run(self, txn): assert False, "something called base class run method" +@HOOK_REGISTRY.add_hook +class ConfigurationError(TransactionHook): + HOOK_GROUPS = frozenset(['unconfigured']) + + def __init__(self, config): + raise errormod.ConfigurationError("testing error") + + @HOOK_REGISTRY.add_hook class TransactionError(TransactionHook): HOOK_GROUPS = frozenset(['configured']) def run(self, txn): - return ['txn:{}'.format(id(txn))] + return [NonError('txn:{}'.format(id(txn)), txn)] @HOOK_REGISTRY.add_hook @@ -43,7 +58,8 @@ class PostingError(TransactionHook): HOOK_GROUPS = frozenset(['configured', 'posting']) def run(self, txn): - return ['post:{}'.format(id(post)) for post in txn.postings] + return [NonError('post:{}'.format(id(post)), txn) + for post in txn.postings] @pytest.fixture @@ -65,8 +81,8 @@ def easy_entries(): def map_errors(errors): retval = {} - for errkey in errors: - key, _, errid = errkey.partition(':') + for error in errors: + key, _, errid = error.message.partition(':') retval.setdefault(key, set()).add(errid) return retval diff --git a/tests/testutil.py b/tests/testutil.py index 264cbd5b703acef3b957cb1ec0fab5990312de92..c021e21493e6d04aa804501ff42fd25dfec36bec 100644 --- a/tests/testutil.py +++ b/tests/testutil.py @@ -20,6 +20,7 @@ import beancount.core.amount as bc_amount import beancount.core.data as bc_data from decimal import Decimal +from pathlib import Path EXTREME_FUTURE_DATE = datetime.date(datetime.MAXYEAR, 12, 30) FUTURE_DATE = datetime.date.today() + datetime.timedelta(days=365 * 99) @@ -94,3 +95,11 @@ class Transaction: else: posting = arg self.postings.append(posting) + + +class TestConfig: + def __init__(self, repo_path=None): + self.repo_path = None if repo_path is None else Path(repo_path) + + def repository_path(self): + return self.repo_path