Skip to content

Commit

Permalink
Isolate hooks between class and subclass (#67)
Browse files Browse the repository at this point in the history
**Why?**
Component hooks are registered on the class that will be instantiated for the component, through a class attribute.
This means that if component `a` is an instance of `class A`, and component `b` an instance of `class B(A)` then the hooks defined on those two components will get mixed.

**What?**
Ensure separation of hooks in cases like the one above by using the class name in the class attribute used to hold hooks.
  • Loading branch information
afallou authored Oct 21, 2019
1 parent 73b423f commit e42f0d6
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
18 changes: 12 additions & 6 deletions microcosm/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,20 @@ class Bar:
ON_RESOLVE = "_microcosm_on_resolve_"


def _invoke_hook(hook_name, target):
def _get_hook_name(hook_prefix, target_cls):
return f"{hook_prefix}_{target_cls.__name__}"


def _invoke_hook(hook_prefix, target_component):
"""
Generic hook invocation.
"""
hook_name = _get_hook_name(hook_prefix, target_component.__class__)
try:
for value in getattr(target, hook_name):
for value in getattr(target_component, hook_name):
func, args, kwargs = value
func(target, *args, **kwargs)
func(target_component, *args, **kwargs)
except AttributeError:
# no hook defined
pass
Expand All @@ -59,16 +64,17 @@ def _invoke_hook(hook_name, target):
pass


def _register_hook(hook_name, target, func, *args, **kwargs):
def _register_hook(hook_prefix, target_cls, func, *args, **kwargs):
"""
Generic hook registration.
"""
hook_name = _get_hook_name(hook_prefix, target_cls)
call = (func, args, kwargs)
try:
getattr(target, hook_name).append(call)
getattr(target_cls, hook_name).append(call)
except AttributeError:
setattr(target, hook_name, [call])
setattr(target_cls, hook_name, [call])


def invoke_resolve_hook(target):
Expand Down
20 changes: 20 additions & 0 deletions microcosm/tests/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ def __init__(self, graph):
self.callbacks = []


@binding("subfoo")
class SubFoo(Foo):
pass


@binding("bar")
def new_foo(graph):
return Foo(graph)


on_resolve(Foo, foo_hook, "baz")
on_resolve(SubFoo, foo_hook, "qux")


class TestHooks:
Expand Down Expand Up @@ -54,6 +60,20 @@ def test_on_resolve_foo_again(self):

assert_that(graph.foo.callbacks, contains("baz"))

def test_on_resolve_foo_subfoo(self):
"""
If we have two components, and one is a subclass of the other's class, we should
still have isolation of the hooks between them
"""
graph = create_object_graph("test")
graph.use("foo")
graph.use("subfoo")
graph.lock()

assert_that(graph.foo.callbacks, contains("baz"))
assert_that(graph.subfoo.callbacks, contains("qux"))

def test_on_resolve_bar_once(self):
"""
Resolving Foo through a separate factory calls the hook.
Expand Down

0 comments on commit e42f0d6

Please sign in to comment.