Repository: kedro-org/kedro

Issue:
Safe guard Hook Argument in `settings.py`
## Description
Is your feature request related to a problem? A clear and concise description of what the problem is: "I'm always frustrated when ..."
Similar to #2768, Hooks are expected to be pass as an instance, but not a class. Beginner often fall into this trap because there are many things expected as a class in `settings.py`, i.e. `DataCatalog`, `OmegaConfigLoader`. Hook is an exception here.

## Possible Implementation
Similar to #2768 do instance check



Discussion:

Patch:
diff --git a/RELEASE.md b/RELEASE.md
index c19418f0..4f2ac763 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -8,6 +8,7 @@
 * Added validation to ensure dataset versions consistency across catalog.
 * Fixed a bug in project creation when using a custom starter template offline.
 * Added `node` import to the pipeline template.
+* Safeguard hooks when user incorrectly registers a hook class in settings.py.
 
 ## Breaking changes to the API
 ## Documentation changes
diff --git a/kedro/framework/hooks/manager.py b/kedro/framework/hooks/manager.py
index ceec0642..cc1e49d8 100644
--- a/kedro/framework/hooks/manager.py
+++ b/kedro/framework/hooks/manager.py
@@ -4,6 +4,7 @@ in a Kedro's execution process.
 
 import logging
 from collections.abc import Iterable
+from inspect import isclass
 from typing import Any
 
 from pluggy import PluginManager
@@ -48,6 +49,11 @@ def _register_hooks(hook_manager: PluginManager, hooks: Iterable[Any]) -> None:
         # case hooks have already been registered, so we perform a simple check
         # here to avoid an error being raised and break user's workflow.
         if not hook_manager.is_registered(hooks_collection):
+            if isclass(hooks_collection):
+                raise TypeError(
+                    "KedroSession expects hooks to be registered as instances. "
+                    "Have you forgotten the `()` when registering a hook class ?"
+                )
             hook_manager.register(hooks_collection)
 
 


Test patch:
diff --git a/tests/framework/hooks/test_manager.py b/tests/framework/hooks/test_manager.py
index 42dc3e9f..6120ba25 100644
--- a/tests/framework/hooks/test_manager.py
+++ b/tests/framework/hooks/test_manager.py
@@ -1,6 +1,11 @@
 import pytest
+from pluggy import PluginManager
 
-from kedro.framework.hooks.manager import _create_hook_manager, _NullPluginManager
+from kedro.framework.hooks.manager import (
+    _create_hook_manager,
+    _NullPluginManager,
+    _register_hooks,
+)
 from kedro.framework.hooks.specs import (
     DataCatalogSpecs,
     DatasetSpecs,
@@ -10,6 +15,10 @@ from kedro.framework.hooks.specs import (
 )
 
 
+class ExampleHook:
+    pass
+
+
 @pytest.mark.parametrize(
     "hook_specs,hook_name,hook_params",
     [
@@ -73,3 +82,23 @@ def test_null_plugin_manager_returns_none_when_called():
     assert (
         plugin_manager.hook.before_dataset_saved(dataset_name="mock", data=[]) is None
     )
+
+
+@pytest.mark.parametrize(
+    "hooks, should_raise",
+    [
+        ([ExampleHook], True),
+        ([ExampleHook()], False),
+    ],
+)
+def test_register_hooks(hooks, should_raise):
+    mock_hook_manager = PluginManager("test_project")
+
+    if should_raise:
+        with pytest.raises(
+            TypeError, match="KedroSession expects hooks to be registered as instances"
+        ):
+            _register_hooks(mock_hook_manager, hooks)
+    else:
+        _register_hooks(mock_hook_manager, hooks)
+        assert mock_hook_manager.is_registered(hooks[0])