Skip to content

Commit

Permalink
Execution hooks (core features) (#508)
Browse files Browse the repository at this point in the history
* Prototype implementation for execution hooks.

This is a prototype implementation for the API proposed in issue #28.

* Fix issue that occurs when the add_operation 'hooks' argument is None.

* Properly handle exceptions for on_start hooks.

* Reduce verbosity of hook module on errors.

* Implement unit tests for run execution hooks.

* Revised hooks implementation and provide basic hook modules.

* Fix docstrings in snapshots module (typo/style).

* Add option to exclude files/directories based on regular expression.

In the snapshot hook module.

* Rename the 'track-operations' executable to 'flow-track-operations'.

* Add fork execution directive.

* Remove fork execution directive.

* Fix rebase error.

* Install hooks through config as callable.

To avoid the need to implement a class and also avoid forcing of a
specific function name.

* Use hidden files for the operations log and tracking log.

This makes it easier to integrate those hook systems with git
tracking.

* Rename '_metadata_schema_version' to '_schema_version'.

* Remove left-over print statement.

* Use git-notes for automated git-tracking.

Instead of storing metadata in the commit message.

* Fix style error.

* Revert changes to base submission script.

* Update changelog.

* Remove obselete internal function.

* Update metadata schema.

* Remove print_function from __future__ import.

No longer needed since the drop of support for Python 2.7.

* fixup! Update metadata schema.

* Rename '.snapshots' directory to '.signac_snapshots'.

* Refactor git repot initialization and git-ignore handling.

Also just ignore the hidden signac snapshots directory instead of
all hidden files and directories.

* Use improved storage layout for snapshot system.

* changes I failed to commit at the time

* Fix issue with test_context unit test.

* Fix error message.

* Fixed some errors made during merge conflict resolution.

* Style changes from pre-commit

* remove a temp todo comment

* Use more descriptive name _hook_triggers

* pre commit formatting

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix typo in docstring link

* Remove flow-track-operations.

* Remove non-core hook features.

* Update changelog.

* Use explicit keyword names.

* Remove from_dict constructor, fix user API to use (name, *jobs) instead of_JobOperation, fix test class name.

* Rename class to _InstallHook.

* Fix comment.

* Remove _install_config_hooks.

* Revert changes to logging configuration. Not needed until the LogOperations hook is introduced.

* Added base hooks test, no error.

* Used a function get get values from dictionary instead of typing the whole thing out. Need to figure out how to run with error and still trigger hook.

* Added test for when hook.on_fail is triggered plus changes to test to make it compatible with this test.

* Added base tests for a cmd function.

* Less clutter and neater in define_hooks_test_project.

* Added hooks install.

* Removed test length of hooks.

* Added cmd tests.

* Added test install test for cmd functions.

* Ignore fail test when exception not raised.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add docstrings to Hooks class.

* flake8 errors.

* Flake8 formatting.

* Update flow/hooks/__init__.py

Co-authored-by: Bradley Dice <[email protected]>

* Moved to changelog for 0.17

* Reference class in docs

Co-authored-by: Bradley Dice <[email protected]>

* Consistent language in the docstring

Co-authored-by: Bradley Dice <[email protected]>

* Input is operation, not operation_name

Co-authored-by: Bradley Dice <[email protected]>

* Language/wording

Co-authored-by: Bradley Dice <[email protected]>

* Missing parenthesis

Co-authored-by: Bradley Dice <[email protected]>

* More accurate description of parameters

Co-authored-by: Bradley Dice <[email protected]>

* Grammar

Co-authored-by: Bradley Dice <[email protected]>

* Directly return the function

Co-authored-by: Bradley Dice <[email protected]>

* Remove unneeded list

Co-authored-by: Bradley Dice <[email protected]>

* Return function directly instead of lambda function

Co-authored-by: Bradley Dice <[email protected]>

* Fix typo (on_failure to on_fail)

Co-authored-by: Bradley Dice <[email protected]>

* Change documentation wording to be more user friendly

Co-authored-by: Bradley Dice <[email protected]>

* Swap order so variable can be used

Co-authored-by: Bradley Dice <[email protected]>

* Use raise instead of raise error for more readable outputs

Co-authored-by: Bradley Dice <[email protected]>

* Various edits to code.

* Added tests for invalid hooks.

* set_job_doc_with_error needed an argument.

* Rename for clairty.

* Renamed for clarity and accuracy.

* Important distinction in docs

Co-authored-by: Carl Simon Adorf <[email protected]>

* More important distinctions in docs.

Co-authored-by: Carl Simon Adorf <[email protected]>

* Correct version number in changelog.

* Renamed _HooksDecorator and added more descriptive docstring.

* Remove unnecessary __call__.

* More changes for clarity on tests.

* Corrected docstring for accuracy.

* Edited string for clarity.

* Fixed typo in test.

* Check for invalid hook attributes before instantiating the project.

* Added test for exceptions in hook.

* Make tests more readable.

* Fixed typo in registry.

* Only test one thing at a time.

* Only test one thing at a time.

* Moved _HookRegistry to class method.

* Moved Hooks and _HooksList class out of __init__ file.

* Added message to describe why.

* Changed from hook to add_hook for clarity.

* Removed unneeded changelong subheadings.

* Raise error with message.

* Renamed from HooksRegistry to HooksRegister because naming consistency with OperationRegister.

* Import hooks without flake8 error

Co-authored-by: Carl Simon Adorf <[email protected]>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Removed unneccessary assert lines.

* Better docs

Co-authored-by: Brandon Butler <[email protected]>

* Used more explicit naming for project vs operation hooks.

* Apply suggestions from code review

Co-authored-by: Bradley Dice <[email protected]>

* Apply suggestions to docstrings.

* Revise docstring.

* Remove metaclass initialization of _operation_hooks.

* Fix code examples for operation hook.

* Rename _hooks to _project_hooks for clarity and symmetry with _operation_hooks.

* Update docs.

* Document operation_hook in API docs.

* Improvements to FlowProject.operation_hook documentation

* Changed to plural project hooks.

* Swapped hook and operation decorators in docstring and tests.

* Forgot to commit change in docstring for operation and hook decorator order.

* Change Hooks to _Hooks

* Update docs with operation_hooks name change

* Flip order of test directives to make operation first

Co-authored-by: Carl Simon Adorf <[email protected]>
Co-authored-by: Kelly Wang <[email protected]>
Co-authored-by: Vyas Ramasubramani <[email protected]>
Co-authored-by: Corwin Kerr <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Kelly Wang <[email protected]>
Co-authored-by: Carl Simon Adorf <[email protected]>
Co-authored-by: Brandon Butler <[email protected]>
  • Loading branch information
9 people authored Dec 29, 2021
1 parent 25d7c61 commit 54c47dd
Show file tree
Hide file tree
Showing 10 changed files with 628 additions and 4 deletions.
10 changes: 10 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ Changelog
The **signac-flow** package follows `semantic versioning <https://semver.org/>`_.
The numbers in brackets denote the related GitHub issue and/or pull request.

Version 0.18

[0.18.0] -- 2022-xx-xx
----------------------

Added
+++++

- Feature to install execution hooks for the automated execution of functions with operations (#28, #189, #508).

Version 0.17
============

Expand Down
18 changes: 17 additions & 1 deletion doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ The FlowProject
FlowProject.make_group
FlowProject.operation
FlowProject.operation.with_directives
FlowProject.operation_hooks
FlowProject.operation_hooks.on_fail
FlowProject.operation_hooks.on_finish
FlowProject.operation_hooks.on_start
FlowProject.operation_hooks.on_success
FlowProject.operations
FlowProject.post
FlowProject.post.copy_from
Expand All @@ -52,6 +57,7 @@ The FlowProject
FlowProject.pre.not_
FlowProject.pre.true
FlowProject.print_status
FlowProject.project_hooks
FlowProject.run
FlowProject.scheduler_jobs
FlowProject.submit
Expand All @@ -60,12 +66,22 @@ The FlowProject
.. autoclass:: FlowProject
:show-inheritance:
:members:
:exclude-members: pre,post,operation
:exclude-members: pre,post,operation,operation_hooks

.. automethod:: flow.FlowProject.operation(func, name=None)

.. automethod:: flow.FlowProject.operation.with_directives(directives, name=None)

.. automethod:: flow.FlowProject.operation_hooks(hook_func, trigger)

.. automethod:: flow.FlowProject.operation_hooks.on_fail

.. automethod:: flow.FlowProject.operation_hooks.on_finish

.. automethod:: flow.FlowProject.operation_hooks.on_start

.. automethod:: flow.FlowProject.operation_hooks.on_success

.. automethod:: flow.FlowProject.post

.. automethod:: flow.FlowProject.post.copy_from
Expand Down
3 changes: 2 additions & 1 deletion flow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
.. _signac: https://signac.io/
"""
from . import environment, errors, scheduling, testing
from . import environment, errors, hooks, scheduling, testing
from .aggregates import aggregator, get_aggregate_id
from .environment import get_environment
from .operations import cmd, directives, with_job
Expand All @@ -24,6 +24,7 @@
__all__ = [
"environment",
"errors",
"hooks",
"scheduling",
"testing",
"aggregator",
Expand Down
7 changes: 7 additions & 0 deletions flow/hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) 2018 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
"""Operation hooks."""
from .hooks import _Hooks

__all__ = ["_Hooks"]
108 changes: 108 additions & 0 deletions flow/hooks/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Copyright (c) 2018 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
"""Operation hooks."""
import logging

logger = logging.getLogger(__name__)


class _HooksList(list):
def __call__(self, *args, **kwargs):
"""Call all hook functions as part of this list."""
for hook in self:
logger.debug(f"Executing hook function '{hook}'.")
try:
hook(*args, **kwargs)
except Exception as error:
logger.error(
"Error occurred during execution of "
"hook '{}': {}.".format(hook, error)
)
raise


class _Hooks:
""":class:`~._Hooks` execute an action or set of actions at specific stages of operation execution.
:class:`~._Hooks` can execute a user defined function when an operation
starts, succeeds, fails, or finishes (regardless of whether the operation
executed successfully or failed).
Hooks can be installed at the operation level as decorators, or on an
instance of :class:`~.FlowProject` through
:meth:`~.FlowProject.project_hooks`.
Examples
--------
The example below shows an operation level decorator that prints the
operation name and job id at the start of the operation execution.
.. code-block:: python
def start_hook(operation_name, job):
print(f"Starting operation {operation_name} on job {job.id}.")
@FlowProject.operation
@FlowProject.operation_hook.on_start(start_hook)
def foo(job):
pass
Parameters
----------
on_start : list of callables
Function(s) to execute before the operation begins execution.
on_finish : list of callables
Function(s) to execute after the operation exits, with or without errors.
on_success : list of callables
Function(s) to execute after the operation exits without error.
on_fail : list of callables
Function(s) to execute after the operation exits with an error.
"""

_hook_triggers = [
"on_start",
"on_finish",
"on_success",
"on_fail",
]

def __init__(self, *, on_start=None, on_finish=None, on_success=None, on_fail=None):
def set_hooks(self, trigger_name, trigger_value):
if trigger_value is None:
trigger_value = []
setattr(self, trigger_name, _HooksList(trigger_value))

set_hooks(self, "on_start", on_start)
set_hooks(self, "on_finish", on_finish)
set_hooks(self, "on_success", on_success)
set_hooks(self, "on_fail", on_fail)

def __setattr__(self, name, value):
"""Convert to _HooksList when setting a hook trigger attribute."""
if name in self._hook_triggers:
super().__setattr__(name, _HooksList(value))
else:
super().__setattr__(name, value)

def update(self, other):
"""Update this instance with hooks from another instance."""
for hook_trigger in self._hook_triggers:
getattr(self, hook_trigger).extend(getattr(other, hook_trigger))

def __str__(self):
"""Return a string representation of all hooks."""
return "{}({})".format(
type(self).__name__,
", ".join(
f"{hook_trigger}={getattr(self, hook_trigger)}"
for hook_trigger in self._hook_triggers
),
)

def __bool__(self):
"""Return True if hooks are defined."""
return any(
getattr(self, hook_trigger, None) for hook_trigger in self._hook_triggers
)
Loading

0 comments on commit 54c47dd

Please sign in to comment.