granola.hooks.base_hook module
This module provides the abstract base class for Hook objects, BaseHook.
BaseHook outlines the framework to design a hook class.
You inherit from
BaseHookspecify your
hooked_classes, which is a list of all the classes that your hook will run on.If you need additional initialization arguments, you can override the default init, passing
attributesandinclude_or_excludetoBaseHookinit.Finally, define what hook methods you need. That might be running hooks
pre_readingorpost_readingor bothYou can also define a hook as a function instead of a class and then “register” it as a hook with the
register_hookdecorator below. This can be very efficient for simple hooks, but it does lose you the ability to run both apre_readingandpost_readingmethod on the same hook, which more complicated hooks maybe need.
Hooks as Classes
Here is an example of a hook class being created
>>> from granola import GettersAndSetters, CannedQueries, BaseHook
>>> class MyHook(BaseHook):
... hooked_classes=[GettersAndSetters, CannedQueries]
... '''This is the list of classes that the hook will be run on.'''
...
... def __init__(self, attributes=["get -sn\r"], include_or_exclude="exclude", some_parameter="default_val"):
... '''attributes and include_or_exclude are parameters (with defaults) that come from
... BaseHook. This specifies which serial commands or other type of attribute will be
... include or exclude on your hook (which will be ran). By saying attributes=["get -sn\r"]
... and include_or_exclude="include" we are saying to exclude "get -sn\r" and
... include all other commands.
... '''
...
... super(MyHook, self).__init__(attributes=attributes, include_or_exclude=include_or_exclude)
... self.some_parameter = some_parameter
... self.calculated_value = None
...
... def pre_reading(self, hooked, data, **kwargs):
... '''store the length of the the incoming command (data) * ``self.some_parameter``'''
... self.calculated_value = self.some_parameter * len(data)
...
... def post_reading(self, hooked, result, data, **kwargs):
... '''Change the serial result to the previous ``self.calculated_value`` * ``len(result)``'''
... result = self.calculated_value * len(result)
... return result
Doing a hook this way, as a class definition allows you to specify a pre_reading and post_reading
method on the same hook. Some hooks (such as the approach hook) rely on it being ran before and after the reading
to know the state before the actual reading, and to change the result for some commands.
Hooks as Functions
If your hook doesn’t require both a pre_reading and post_reading, you can instead just write
a function and decorate it with the register_hook() decorator.
Here is an example of a hook being created
>>> from granola import register_hook, CannedQueries
>>> @register_hook(hook_type_enum="post_reading", hooked_classes=[CannedQueries])
... def LoopCannedQueries(hooked, result, data, **kwargs):
... if result is SENTINEL:
... hooked._start_serial_generator(data)
... result = next(hooked.serial_generator[data])
... return result
... return result
This is the actual implementation of LoopCannedQueries the looping hook that by
default loops all canned queries (minus the docstring)
register_hook() takes two arguments, the hook_type_enum
(see HookTypes for options and more details), which tells you if it is a
pre or post_reading hook (can be passed in as a direct enum object or as a string), and the hooked_classes,
which tell you which command_readers the hook applies to.
After that it is just your function definition.
The signature of your function should match the signature of base method you are using
(post_reading, post_reading, etc.) from BaseHook.
After you have “registered” your hook function, it can then be initialized like a hook class.
(i.e. substantiating it before you pass it to Cereal). If you don’t instantiate it, then Cereal
will instantiate it with default arguments, this allows you to treat it as a regular function being passed
into Cereal, and not worry about what else happens with the decorator
Hook Examples
>>> from granola import LoopCannedQueries, Cereal
>>> command_readers = {"CannedQueries": {"data": [{"cmd\r": "response"}]}}
>>> # this way works by initializing LoopCannedQueries to non default values
>>> hook = LoopCannedQueries(attributes={"get batt\r"}, include_or_exclude="include")
>>> cereal = Cereal(command_readers=command_readers, hooks=[hook])
>>> # This version we initialize it to its default values
>>> hook = LoopCannedQueries()
>>> ccereal = Cereal(command_readers=command_readers, hooks=[hook])
>>> # This version we don't initialize it at all and let Cereal initialize it to its default values
>>> hook = LoopCannedQueries
>>> cereal = Cereal(command_readers=command_readers, hooks=[hook])
These examples also shows how to pass hooks to Cereal. You pass hooks in an iterable to
Cereal, and Cereal will associate the hook with whatever class it needs to
based on the classes listed in your hook’s hooked_classes. In these example, LoopCannedQueries would
only run on CannedQueries, but MyHook would run on CannedQueries and GettersAndSetters.
One final note, SENTINEL is a special book keeping object for special unhandled responses that is different from a None response.
- class granola.hooks.base_hook.BaseHook(attributes=None, include_or_exclude=SetRelationship.exclude, *args, **kwargs)
Bases:
ABC- hooked_classes = []
- post_reading(hooked, result, data, **kwargs)
Run the post_reading hook
- Parameters
hooked – instance of hooked class
result (str) – Returned result from serial command.
data (str) – Serial command
- Returns
result (str)
- pre_reading(hooked, data, **kwargs)
Run the pre_reading hook
- Parameters
hooked – instance of hooked class
data (str) – Serial command
- Returns
data (str)
- granola.hooks.base_hook.register_hook(hook_type_enum, hooked_classes)
Register a function as a BaseHook subclass (this converts a function into a subclass of BaseHook. hook_type is the method(s) to insert your function as in the created subclass. (example, if you say hook_types=”pre_reading”, then the decorated function will become the pre_reading method in the returned Hook class.
Options for hook_type are any enum in HookTypes. hooked_classes are the classes that you wish to run this hook on.
- Parameters
hook_type (str | HookTypes) – str representation of HookTypes or HookTypes instances themselves you wish to run this hook as. Your option(s) will be coverted to methods in the returned class.
hooked_classes (list[BaseCommandReaders]) – list of BaseCommandReaders that you wish to run this hook on.
- granola.hooks.base_hook.wrap_in_hooks(func)
Decorator that wraps a function in hooks that will run pre and post function.
- wrapper args:
hooked (CannedQueries): CannedQueries instance to run the hook on. result (str): Passed in result to potentially modify. data (str): Serial command.