Strict Hook/Filter Registration Extension¶
The Strict Registration Extension enforces fail-fast validation when registering hooks and filters, preventing silent failures from typos or undefined hook/filter names.
Overview¶
Default Behavior: All hooks/filters MUST be defined before registration. Attempts to register to undefined hooks/filters raise FrameworkError immediately.
Key Features:
Fail-fast validation: Catches typos at startup, not runtime
Fuzzy matching suggestions: “Did you mean X?” error messages
Plugin identification: Shows which plugin caused the error
Optional escape hatch:
@optional()decorator for truly optional registrations
Why Strict-by-Default?¶
Before (Cement default):
[DEBUG] filter name 'INFERENCE_PREPROCESS_PRE_FILTR' is not defined! ignoring...
# Application continues
# Filter never executes
# Silent failure = hard to debug
After (Strict mode):
[ERROR] Filter 'INFERENCE_PREPROCESS_PRE_FILTR' is not defined.
Did you mean: INFERENCE_PREPROCESS_PRE_FILTER?
FrameworkError: Filter 'INFERENCE_PREPROCESS_PRE_FILTR' is not defined.
# Application stops immediately
# Error is obvious
Configuration¶
The extension is automatically loaded in vipr/main.py:
extensions = [
'vipr.ext.strict_registration', # Strict registration
'vipr.ext.dynamic_hooks_filters', # Dynamic YAML loading
]
Important: strict_registration MUST load BEFORE dynamic_hooks_filters to enforce validation.
Usage¶
Default: Strict Validation¶
All registrations are validated:
app.hook.register('TYPO_HOOK', my_func)
# ❌ FrameworkError: Hook 'TYPO_HOOK' is not defined.
vipr:
inference:
filters:
INFERENCE_PREPROCESS_PRE_FILTR: # Typo!
- class: MyPlugin
method: my_method
# ❌ Error at startup with suggestions
Optional Registration: Two Methods¶
When a hook/filter might legitimately not exist (e.g., optional features):
Method 1: @optional() Decorator (Code)¶
from vipr.ext.strict_registration import optional
from vipr.plugins.discovery.decorators import discover_filter
class ExperimentalPlugin:
@optional()
@discover_filter('EXPERIMENTAL_FEATURE_FILTER')
def experimental_feature(self, data, **kwargs):
"""Only runs if the filter namespace exists."""
return data
# No special config needed
EXPERIMENTAL_FEATURE_FILTER:
- class: MyPlugin
method: experimental_feature
# ⚠️ Warning if filter doesn't exist, but continues
Method 2: optional: true Flag (YAML)¶
from vipr.plugins.discovery.decorators import discover_filter
class ExperimentalPlugin:
@discover_filter('EXPERIMENTAL_FEATURE_FILTER')
def experimental_feature(self, data, **kwargs):
return data
EXPERIMENTAL_FEATURE_FILTER:
- class: MyPlugin
method: experimental_feature
optional: true # ← Set by dynamic_hooks_filters, evaluated by strict_registration
# ⚠️ Warning if filter doesn't exist, but continues
Choosing Between Methods¶
Aspect |
|
|
|---|---|---|
Where |
Python code |
YAML config |
Flexibility |
Static |
Dynamic per deployment |
Best for |
Architectural decisions |
Configuration variations |
Use @optional() when:
Feature is always optional (architectural decision)
Want code as single source of truth
Use optional: true when:
Optionality varies per deployment/environment
Need configuration flexibility
Error Messages¶
Typo Detection¶
FrameworkError: Filter 'INFERENCE_PREPROCESS_PRE_FILTER2' is not defined.
Attempted registration from plugin 'reflectorch_extension'.
Did you mean one of these?
- INFERENCE_PREPROCESS_PRE_FILTER
- INFERENCE_PREPROCESS_POST_FILTER
- INFERENCE_POSTPROCESS_PRE_FILTER
Plugin Identification¶
Error messages identify the source plugin:
Attempted registration from plugin 'my_plugin'.
Integration with Dynamic Hooks/Filters¶
The optional: true YAML flag is processed by the Dynamic Hooks/Filters extension and checked by Strict Registration:
# In dynamic_hooks_filters extension:
if cfg.get("optional", False):
callback.__optional__ = True # Set attribute
# In strict_registration extension:
if hasattr(func, '__optional__') and func.__optional__:
# Only warning, no error
return False
See Dynamic Hooks/Filters Extension for YAML configuration details.
Use Cases¶
✅ Good Use Cases for @optional()¶
Plugin compatibility across versions:
from vipr.ext.strict_registration import optional
from vipr.plugins.discovery.decorators import discover_hook
class StreamingCompatPlugin:
@optional()
@discover_hook('VIPR_2_0_STREAMING_HOOK')
def use_streaming_feature(self, app, **kwargs):
"""Works with VIPR 1.x (ignored) and 2.x (executed)."""
pass
Experimental features:
EXPERIMENTAL_VISUALIZATION_HOOK:
- class: vipr.plugins.viz.Visualizer
method: plot_results
optional: true # Not all installations have viz plugin
Conditional workflows:
from vipr.ext.strict_registration import optional
from vipr.plugins.discovery.decorators import discover_filter
class AdvancedPostprocessPlugin:
@optional()
@discover_filter('ADVANCED_POSTPROCESS_FILTER')
def advanced_postprocessing(self, data, **kwargs):
"""Only if advanced plugin installed."""
return data
❌ Bad Use Cases¶
Hiding typos:
# WRONG! Fix the typo instead
INFERENCE_PREPROCESS_PRE_FILTR: # Should be INFERENCE_PREPROCESS_PRE_FILTER
- class: my_plugin.preprocessing.PreprocessPlugin
method: preprocess
optional: true # ← Don't hide bugs!
Core features:
# WRONG! Core features should be required
from vipr.ext.strict_registration import optional
from vipr.plugins.discovery.decorators import discover_filter
class NormalizerPlugin:
@optional()
@discover_filter('INFERENCE_PREPROCESS_PRE_FILTER')
def normalize(self, data, **kwargs):
return data
Troubleshooting¶
“Hook/Filter ‘X’ is not defined”¶
Cause: Typo in hook/filter name or hook not defined in framework.
Solution:
Check error message for suggestions
Verify namespace is defined with
app.hook.define('HOOK_NAME')orapp.filter.define('FILTER_NAME')If truly optional, add
optional: trueor@optional()
Optional callback not executing¶
Cause: Hook/filter doesn’t exist and callback is marked optional.
Solution: This is expected behavior. Check logs for warning message.
Attribute propagation issues¶
Cause: Custom wrappers not propagating __optional__ attribute.
Solution: Ensure wrappers use _propagate_attributes() from dynamic_hooks_filters.
Best Practices¶
Default to strict: Only use
@optional()when truly neededFix typos: Don’t hide bugs with
optional: trueDocument optionality: Comment why a callback is optional
Choose one method: Don’t mix
@optional()andoptional: truefor same callbackTest both paths: Test with and without optional hooks/filters loaded
Use class methods for discovery decorators:
@discover_hook/@discover_filterreject standalone functions
Technical Details¶
Implementation¶
The extension monkey-patches Cement’s registration methods:
def enhanced_hook_register(name, func, weight=0):
if name not in app.hook.__hooks__:
if hasattr(func, '__optional__') and func.__optional__:
# Log warning, return False
return False
# Raise FrameworkError with suggestions
raise FrameworkError(error_msg)
return original_hook_register(name, func, weight)
Attribute Propagation¶
The __optional__ attribute is propagated through wrapper functions in dynamic_hooks_filters:
def _propagate_attributes(source_cb, target_cb):
for attr in ("__name__", "__module__", "__optional__"):
if hasattr(source_cb, attr):
setattr(target_cb, attr, getattr(source_cb, attr))
Load Order¶
Critical: Extension must load BEFORE dynamic_hooks_filters:
extensions = [
'vipr.ext.strict_registration', # 1. Patches registration
'vipr.ext.dynamic_hooks_filters', # 2. Uses patched methods
]
Migration Guide¶
Breaking Change: Strict validation now fails immediately on undefined hooks/filters or typos.
Quick Fix:
Run your app - errors show typos with “Did you mean…” suggestions
Fix the typo in config/code, or
Mark as optional with
optional: true(YAML) or@optional()(code)
Example Error:
FrameworkError: Filter 'INFERENCE_PREDICTION_PRE_FILTR' is not defined.
Did you mean: INFERENCE_PREDICTION_PRE_FILTER?
Solution: Fix typo in YAML or mark genuinely optional features with optional: true.
See Also¶
Dynamic Hooks/Filters Extension - YAML configuration
Inference Plugin - Hook/filter workflow
vipr-core/vipr/ext/strict_registration.py- Implementation