Coverage for mcpgateway / plugins / framework / decorator.py: 100%
21 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
1# -*- coding: utf-8 -*-
2"""Location: ./mcpgateway/plugins/framework/decorator.py
3Copyright 2025
4SPDX-License-Identifier: Apache-2.0
5Authors: Teryl Taylor
7Hook decorator for dynamically registering plugin hooks.
9This module provides decorators for marking plugin methods as hook handlers.
10Plugins can use these decorators to:
111. Override the default hook naming convention
122. Register custom hooks not in the standard framework
14Examples:
15 Override hook method name::
17 class MyPlugin(Plugin):
18 @hook(ToolHookType.TOOL_PRE_INVOKE)
19 def custom_name_for_tool_hook(self, payload, context):
20 # This gets called for tool_pre_invoke even though
21 # the method name doesn't match
22 return ToolPreInvokeResult(continue_processing=True)
24 Register a completely new hook type::
26 class MyPlugin(Plugin):
27 @hook("custom_pre_process", CustomPayload, CustomResult)
28 def my_custom_hook(self, payload, context):
29 # This registers a new hook type dynamically
30 return CustomResult(continue_processing=True)
32 Use default convention (no decorator needed)::
34 class MyPlugin(Plugin):
35 def tool_pre_invoke(self, payload, context):
36 # Automatically recognized by naming convention
37 return ToolPreInvokeResult(continue_processing=True)
38"""
40# Standard
41from typing import Callable, Optional, Type, TypeVar
43# Third-Party
44from pydantic import BaseModel
46# First-Party
47from mcpgateway.plugins.framework.models import PluginPayload, PluginResult
49# Attribute name for storing hook metadata on functions
50_HOOK_METADATA_ATTR = "_plugin_hook_metadata"
52# Type vars for type hints
53P = TypeVar("P", bound=PluginPayload) # Payload type
54R = TypeVar("R", bound=PluginResult) # Result type
57class HookMetadata:
58 """Metadata stored on decorated hook methods.
60 Attributes:
61 hook_type: The hook type identifier (e.g., 'tool_pre_invoke')
62 payload_type: Optional payload class for hook registration
63 result_type: Optional result class for hook registration
64 """
66 def __init__(
67 self,
68 hook_type: str,
69 payload_type: Optional[Type[BaseModel]] = None,
70 result_type: Optional[Type[BaseModel]] = None,
71 ):
72 """Initialize hook metadata.
74 Args:
75 hook_type: The hook type identifier
76 payload_type: Optional payload class for registering new hooks
77 result_type: Optional result class for registering new hooks
78 """
79 self.hook_type = hook_type
80 self.payload_type = payload_type
81 self.result_type = result_type
84def hook(
85 hook_type: str,
86 payload_type: Optional[Type[P]] = None,
87 result_type: Optional[Type[R]] = None,
88) -> Callable[[Callable], Callable]:
89 """Decorator to mark a method as a plugin hook handler.
91 This decorator attaches metadata to a method so the Plugin class can
92 discover it during initialization and register it with the appropriate
93 hook type.
95 Args:
96 hook_type: The hook type identifier (e.g., 'tool_pre_invoke')
97 payload_type: Optional payload class for registering new hook types
98 result_type: Optional result class for registering new hook types
100 Returns:
101 Decorator function that marks the method with hook metadata
103 Examples:
104 Override method name::
106 @hook(ToolHookType.TOOL_PRE_INVOKE)
107 def my_custom_method_name(self, payload, context):
108 return ToolPreInvokeResult(continue_processing=True)
110 Register new hook type::
112 @hook("email_pre_send", EmailPayload, EmailResult)
113 def handle_email(self, payload, context):
114 return EmailResult(continue_processing=True)
115 """
117 def decorator(func: Callable) -> Callable:
118 """Inner decorator that attaches metadata to the function.
120 Args:
121 func: The function to decorate
123 Returns:
124 The same function with metadata attached
125 """
126 # Store metadata on the function object
127 metadata = HookMetadata(hook_type, payload_type, result_type)
128 setattr(func, _HOOK_METADATA_ATTR, metadata)
129 return func
131 return decorator
134def get_hook_metadata(func: Callable) -> Optional[HookMetadata]:
135 """Get hook metadata from a decorated function.
137 Args:
138 func: The function to check
140 Returns:
141 HookMetadata if the function is decorated, None otherwise
143 Examples:
144 >>> @hook("test_hook")
145 ... def test_func():
146 ... pass
147 >>> metadata = get_hook_metadata(test_func)
148 >>> metadata.hook_type
149 'test_hook'
150 >>> get_hook_metadata(lambda: None) is None
151 True
152 """
153 return getattr(func, _HOOK_METADATA_ATTR, None)
156def has_hook_metadata(func: Callable) -> bool:
157 """Check if a function has hook metadata.
159 Args:
160 func: The function to check
162 Returns:
163 True if the function is decorated with @hook, False otherwise
165 Examples:
166 >>> @hook("test_hook")
167 ... def decorated():
168 ... pass
169 >>> has_hook_metadata(decorated)
170 True
171 >>> has_hook_metadata(lambda: None)
172 False
173 """
174 return hasattr(func, _HOOK_METADATA_ATTR)