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_STRAT_HOOK' is not defined! ignoring...
# Application continues
# Filter never executes
# Silent failure = hard to debug
After (Strict mode):
[ERROR] Filter 'INFERENCE_STRAT_HOOK' is not defined.
Did you mean: INFERENCE_START_HOOK?
FrameworkError: Filter 'INFERENCE_STRAT_HOOK' 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_STRAT_HOOK: # 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
@optional()
@discover_filter('EXPERIMENTAL_FEATURE_FILTER')
def experimental_feature(self, data, **kwargs):
"""Only runs if experimental plugin loaded"""
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)¶
# No decorator needed
@discover_filter('EXPERIMENTAL_FEATURE_FILTER')
def experimental_feature(self, data, **kwargs):
return data
EXPERIMENTAL_FEATURE_FILTER:
- class: MyPlugin
method: experimental_feature
optional: true # ← Handled 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 'reflectometry'.
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:
@optional()
@discover_hook('VIPR_2_0_STREAMING_HOOK')
def use_streaming_feature(app):
"""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:
@optional()
@discover_filter('ADVANCED_POSTPROCESS_FILTER')
def advanced_postprocessing(data):
"""Only if advanced plugin installed"""
return data
❌ Bad Use Cases¶
Hiding typos:
# WRONG! Fix the typo instead
INFERENCE_STRAT_HOOK: # Should be START
- method: my_method
optional: true # ← Don't hide bugs!
Core features:
# WRONG! Core features should be required
@optional()
@discover_filter('INFERENCE_NORMALIZE_FILTER')
def normalize(data):
pass
Troubleshooting¶
“Hook ‘X’ is not defined”¶
Cause: Typo in hook/filter name or hook not defined in framework.
Solution:
Check error message for suggestions
Verify hook is defined with
app.hook.define('HOOK_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
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_PREDICITON_FILTER' is not defined.
Did you mean: INFERENCE_PREDICTION_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