Creating Hooks
This document covers all aspects of writing hooks focusing on the API and it’s semantics for how it can be used within tackle files. For creating providers and creating dependencies, please check out the creating providers section.
Overview
Tackle box hooks are any object located within the hooks
directory that extends a BaseHook object and implement an execute
method as the entrypoint to calling the hook. BaseHook objects are pydantic objects as well such that their attributes need to include type annotations. All of these attributes are then made accessible when calling the hook from a yaml file.
There are couple other semantics that will be described in this document such as mapping arguments, excluding from rendering, and auto-generating documentation for your hooks that will be covered later in this document.
Basic Example
The easiest way to understand this is through a simple example.
If we had a file structure like this:
├── hooks
└── do_stuff.py # Name of file doesn't matter
└── tackle.yaml # Not needed
We could have a file do_stuff.py
that has an object DoStuffHook
that extends the BaseHook
and implements an execute
method which in this case both prints and returns the things
attribute. Additionally there is a private _args
attribute which can be used to map positional arguments to an attribute, in this case things
(more on this later).
from tackle import BaseHook, Field
class DoStuffHook(BaseHook):
type: str = "do-stuff"
things: str = Field(None, description="All the things.")
_args: list = ['things']
def execute(self):
print(self.things)
return self.things
One could then run a tackle file that looks like this:
compact-expression->: do-stuff All the things
expanded-expression:
->: do-stuff
things: All the things
Which when run would print out “All the things” twice and return the following context.
compact-expression: All the things
expanded-expression: All the things
Concepts
Pydantic and Types / Fields
Pydantic has some idioms to be aware of when writing hooks specifically around types and fields. Every attribute needs to be declared with a type in pydantic and will throw an error if the type is not explicitly declared within the attribute’s definition or if the wrong type is fed into the field. Because everything that is output from a hook needs to serializable (i.e. it can’t return python objects), many pydantic types aren’t usable unless they can be directly serialized back into a structured data format (i.e. a string, int, float, list, or dict).
Multiple types for attributes are allowed by use of the Union
or Optional
types (see difference) so that within the execute statement one can qualify the type and process it appropriately. For instance:
from tackle import BaseHook, Field
from typing import Union
class DoStuffHook(BaseHook):
type: str = "do-stuff"
things: Union[str] = None
_args: list = ['things']
def execute(self):
if isinstance(self.things, list):
for i in self.things:
print(f"Thing = {i}")
elif isinstance(self.things, str):
print(self.things)
return self.things
[ ] TODO
Arguments
As you may have noticed, tackle-box supports two general types of hook calls, compact and expanded. The only way compact expressions are able to take arguments is through mapping those arguments to fields within hooks. This is done by making an _args
private attribute which is a list of strings pointing to the attributes the indexed arguments relate to. For instance from our example before:
class DoStuffHook(BaseHook):
type: str = "do-stuff"
things: str = None
more_things: str = None
_args: list = ['things', 'more_things']
Now we are adding a field more_things
which is another string that one could call like:
compact-expression->: do-stuff All the things
Would be the equivalent of:
expanded-expression:
->: do-stuff
things: All
more_things: the things
This is because the first argument “All” after the hook type “do-stuff” is not encapsulated with quotes and so arguments after it are grouped together. If we wanted to call it with two distinct attributes, we’d need to put quotes around them like this:
compact-expression->: do-stuff "All the things" "with other stuff"
Would be the equivalent of:
expanded-expression:
->: do-stuff
things: All the things
more_things: with other stuff
Arguments with list and dict types
[ ] TODO
Controlling Rendering of Fields
As of the time of this writing,
validator
objects are not supported but hopefully will be in the future