diff --git a/CODE.rst b/CODE.rst index 7532752..abeafb0 100644 --- a/CODE.rst +++ b/CODE.rst @@ -59,6 +59,9 @@ Hooks make arbitrary transformations to entry data dicts. Every entry data dict If this method returns any other value, the program replaces the entry data with the return value, and continues processing. +Class attribute ``KIND`` + This should be one of the values of the ``hooks.HOOK_KINDS`` enum. This information determines what order hooks run in. + Templates ~~~~~~~~~ diff --git a/TODO.rst b/TODO.rst index 6210f26..0185219 100644 --- a/TODO.rst +++ b/TODO.rst @@ -8,10 +8,6 @@ The big idea: make it easier for hooks to customize *what* template(s) are rende Required: -* Add some sort of ordering for hooks - Thinking an enum of named stages: data adders, data mungers, filters, actions. - The return value of ``hooks.load_all()`` must respect this ordering. - * Make the main loop seed the entry data with information about the importer used. * Move template rendering into a hook, where the template to load is determined by a value in the entry data. diff --git a/import2ledger/hooks/__init__.py b/import2ledger/hooks/__init__.py index 8cfa5cb..0e7caa1 100644 --- a/import2ledger/hooks/__init__.py +++ b/import2ledger/hooks/__init__.py @@ -1,6 +1,27 @@ import operator +try: + import enum +except ImportError: + import enum34 as enum + from .. import dynload +HOOK_KINDS = enum.Enum('HOOK_KINDS', [ + # Hooks will run in the order that their KIND appears in this list. + + # DATA_ADDER hooks should add data to the entry from outside sources like + # the user's configuration. + 'DATA_ADDER', + # DATA_MUNGER hooks should add or change data in the entry based on what's + # already in it. + 'DATA_MUNGER', + # DATA_FILTER hooks make a decision about whether or not to proceed with + # processing the entry. + 'DATA_FILTER', +]) + def load_all(): - return dynload.submodule_items_named(__file__, operator.methodcaller('endswith', 'Hook')) + hooks = list(dynload.submodule_items_named(__file__, operator.methodcaller('endswith', 'Hook'))) + hooks.sort(key=operator.attrgetter('KIND.value')) + return hooks diff --git a/import2ledger/hooks/add_entity.py b/import2ledger/hooks/add_entity.py index a6b44a8..20432ce 100644 --- a/import2ledger/hooks/add_entity.py +++ b/import2ledger/hooks/add_entity.py @@ -1,7 +1,10 @@ import re import unicodedata +from . import HOOK_KINDS + class AddEntityHook: + KIND = HOOK_KINDS.DATA_MUNGER NAME_PREFIXES = frozenset([ 'da', 'de', diff --git a/import2ledger/hooks/default_date.py b/import2ledger/hooks/default_date.py index 4e787c3..9aa0ae4 100644 --- a/import2ledger/hooks/default_date.py +++ b/import2ledger/hooks/default_date.py @@ -1,4 +1,8 @@ +from . import HOOK_KINDS + class DefaultDateHook: + KIND = HOOK_KINDS.DATA_ADDER + def __init__(self, config): self.config = config diff --git a/import2ledger/hooks/filter_by_date.py b/import2ledger/hooks/filter_by_date.py index 78e35c5..235fe63 100644 --- a/import2ledger/hooks/filter_by_date.py +++ b/import2ledger/hooks/filter_by_date.py @@ -1,4 +1,8 @@ +from . import HOOK_KINDS + class FilterByDateHook: + KIND = HOOK_KINDS.DATA_FILTER + def __init__(self, config): self.config = config diff --git a/import2ledger/hooks/invoice_payment.py b/import2ledger/hooks/invoice_payment.py index 826c28a..ebbd90e 100644 --- a/import2ledger/hooks/invoice_payment.py +++ b/import2ledger/hooks/invoice_payment.py @@ -1,4 +1,8 @@ +from . import HOOK_KINDS + class InvoicePaymentHook: + KIND = HOOK_KINDS.DATA_MUNGER + def __init__(self, config): pass diff --git a/setup.py b/setup.py index 8d8be50..2afb12d 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import itertools +import sys from setuptools import setup, find_packages @@ -12,6 +13,9 @@ REQUIREMENTS = { }, } +if sys.version_info < (3, 4): + REQUIREMENTS['install_requires'].extend(['enum34']) + REQUIREMENTS['tests_require'] = [ 'pytest', 'PyYAML', diff --git a/tests/test_hooks.py b/tests/test_hooks.py index bcb7e3e..d5d61d7 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -9,7 +9,9 @@ from import2ledger.hooks import add_entity, default_date, filter_by_date def test_load_all(): all_hooks = list(hooks.load_all()) - assert add_entity.AddEntityHook in all_hooks + positions = {hook: index for index, hook in enumerate(all_hooks)} + assert positions[default_date.DefaultDateHook] < positions[add_entity.AddEntityHook] + assert positions[add_entity.AddEntityHook] < positions[filter_by_date.FilterByDateHook] @pytest.mark.parametrize('in_key,payee,out_key,expected', [ ('payee', 'Alex Smith', 'entity', 'Smith-Alex'),