plugin: Add HookRegistry.load_included_hooks() method.

This lets us import the plugin module without importing all of the included
hooks. This provides better isolation and error reporting in case there's
something like a syntax problem in one of the hooks: it doesn't cause
importing any plugin module to fail.
This commit is contained in:
Brett Smith 2020-04-07 13:31:09 -04:00
parent fdb62dd1c6
commit 0bf44ade7a
2 changed files with 32 additions and 15 deletions

View file

@ -45,6 +45,20 @@ from ..errors import (
__plugins__ = ['run']
class HookRegistry:
INCLUDED_HOOKS: Dict[str, Optional[List[str]]] = {
'.meta_approval': None,
'.meta_entity': None,
'.meta_expense_allocation': None,
'.meta_income_type': None,
'.meta_invoice': None,
'.meta_project': None,
'.meta_receipt': None,
'.meta_receivable_documentation': None,
'.meta_repo_links': None,
'.meta_rt_links': ['MetaRTLinks'],
'.meta_tax_implication': None,
}
def __init__(self) -> None:
self.group_name_map: Dict[HookName, Set[Type[Hook]]] = {
t.__name__: set() for t in ALL_DIRECTIVES
@ -61,7 +75,7 @@ class HookRegistry:
def import_hooks(self,
mod_name: str,
*hook_names: str,
package: Optional[str]=__module__, # type:ignore[name-defined]
package: Optional[str]=None,
) -> None:
if not hook_names:
_, _, hook_name = mod_name.rpartition('.')
@ -70,6 +84,10 @@ class HookRegistry:
for hook_name in hook_names:
self.add_hook(getattr(module, hook_name))
def load_included_hooks(self) -> None:
for mod_name, hook_names in self.INCLUDED_HOOKS.items():
self.import_hooks(mod_name, *(hook_names or []), package=self.__module__)
def group_by_directive(self, config_str: str='') -> Iterable[Tuple[HookName, Type[Hook]]]:
config_str = config_str.strip()
if not config_str:
@ -96,25 +114,15 @@ class HookRegistry:
yield key, hook
HOOK_REGISTRY = HookRegistry()
HOOK_REGISTRY.import_hooks('.meta_approval')
HOOK_REGISTRY.import_hooks('.meta_entity')
HOOK_REGISTRY.import_hooks('.meta_expense_allocation')
HOOK_REGISTRY.import_hooks('.meta_income_type')
HOOK_REGISTRY.import_hooks('.meta_invoice')
HOOK_REGISTRY.import_hooks('.meta_project')
HOOK_REGISTRY.import_hooks('.meta_receipt')
HOOK_REGISTRY.import_hooks('.meta_receivable_documentation')
HOOK_REGISTRY.import_hooks('.meta_repo_links')
HOOK_REGISTRY.import_hooks('.meta_rt_links', 'MetaRTLinks')
HOOK_REGISTRY.import_hooks('.meta_tax_implication')
def run(
entries: List[Directive],
options_map: Dict[str, Any],
config: str='',
hook_registry: HookRegistry=HOOK_REGISTRY,
hook_registry: Optional[HookRegistry]=None,
) -> Tuple[List[Directive], List[Error]]:
if hook_registry is None:
hook_registry = HookRegistry()
hook_registry.load_included_hooks()
errors: List[Error] = []
hooks: Dict[HookName, List[Hook]] = {
# mypy thinks NamedTuples don't have __name__ but they do at runtime.

View file

@ -112,6 +112,15 @@ def test_registry_unknown_group_name():
with pytest.raises(ValueError):
next(HOOK_REGISTRY.group_by_directive('UnKnownTestGroup'))
def test_registry_load_included_hooks():
registry = plugin.HookRegistry()
assert not list(registry.group_by_directive())
registry.load_included_hooks()
actual = {hook.__name__ for key, hook in registry.group_by_directive() if key == 'Transaction'}
assert len(actual) >= 5
assert 'MetaProject' in actual
assert 'MetaRTLinks' in actual
def test_run_with_multiple_hooks(easy_entries, config_map):
out_entries, errors = plugin.run(easy_entries, config_map, '', HOOK_REGISTRY)
assert len(out_entries) == 2