Changeset - 0bf44ade7aee
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-04-07 17:31:09
brettcsmith@brettcsmith.org
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.
2 files changed with 32 insertions and 15 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/plugin/__init__.py
Show inline comments
...
 
@@ -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.
tests/test_plugin.py
Show inline comments
...
 
@@ -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
0 comments (0 inline, 0 general)