Plugin overload — replacing and restoring an existing handler
This notebook demonstrates the overload=True parameter of register_action().
Scenario |
|
Result |
|---|---|---|
New action |
|
Silent registration |
Existing action — permanent replacement |
|
|
Existing action — temporary replacement |
|
|
The concrete example here: we install three successive versions of an rdown handler on the same action, stacking and unstacking them with overload=True.
1 — wx startup
Always use
%gui wxin a notebook — neverwx.App().
[1]:
import logging, sys
# Show WARNING/INFO in the cell output
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(message)s')
%gui wx
2 — Viewer
[2]:
from wolfhece.PyDraw import WolfMapViewer
viewer = WolfMapViewer(None, title="Overload demo", w=1200, h=800)
viewer.Show()
DEBUG Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Found MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Found FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
INFO Executable paths loaded from cache
DEBUG Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG Searching MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Found MKL in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Scripts\Library\bin
DEBUG Searching FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG Found FORTRAN in c:\Users\pierre\Documents\Gitlab\python311\Library\bin
DEBUG OpenGL_accelerate module loaded
DEBUG Using accelerated ArrayDatatype
INFO Using NT-specific GLUT calls with exit callbacks
INFO Importing wolfhece modules
INFO wolfhece modules imported
INFO WinTab : contexte ouvert sur HWND=0x2870A3A (pressure_max=32767)
[2]:
False
3 — Case 1: simple registration (new action)
No conflict → no warning.
[3]:
from wolfhece._viewer_plugin_handlers import MouseContext
def _rdown_v1(v: WolfMapViewer, ctx: MouseContext) -> None:
print(f"[v1] X={ctx.x_snap:.3f} Y={ctx.y_snap:.3f}")
# First registration — no WARNING expected
viewer.register_action('my demo', rdown_handler=_rdown_v1)
viewer.start_action('my demo', 'Right-click → version 1')
print("Active handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
print("_saved_handlers:", viewer._saved_handlers)
INFO ACTION : Right-click → version 1
Active handler: _rdown_v1
_saved_handlers: {}
4 — Case 2: replacement without saving (overload=False)
A WARNING is emitted — _rdown_v1 will be lost after replacement.
[ ]:
def _rdown_v2(v: WolfMapViewer, ctx: MouseContext) -> None:
print(f"[v2] snap=({ctx.x_snap:.3f}, {ctx.y_snap:.3f})")
# overload=False (default) → WARNING, _rdown_v1 lost
viewer.register_action('my demo', rdown_handler=_rdown_v2)
print("Active handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
print("_saved_handlers:", viewer._saved_handlers) # still empty
WARNING register_action: 'my demo' — rdown handler already registered (previous '_rdown_v1' will be lost; pass overload=True to save it).
Active handler: _rdown_v2
_saved_handlers: {}
[v2] snap=(21.491, 24.980)
[v2] snap=(13.449, 22.264)
[v2] snap=(10.466, 17.949)
[v2] snap=(17.816, 14.967)
5 — Case 3: overload with saving (overload=True)
_rdown_v3 on top of _rdown_v2.WARNING is emitted and _rdown_v2 is saved in _saved_handlers.unregister_action, _rdown_v2 will be automatically restored.[ ]:
def _rdown_v3(v: WolfMapViewer, ctx: MouseContext) -> None:
"""Temporary diagnostic handler — logs pixel and world coordinates."""
print(f"[v3 DIAG] pixel=({ctx.x_pixel},{ctx.y_pixel}) "
f"world=({ctx.x:.3f},{ctx.y:.3f})")
# overload=True → WARNING + save _rdown_v2
viewer.register_action('my demo', rdown_handler=_rdown_v3, overload=True)
print("\nActive handler:", viewer._custom_rdown_handlers.get('my demo').__name__)
saved = viewer._saved_handlers.get('my demo', {})
saved_rdown = saved.get('rdown')
print("Saved handler (rdown):",
saved_rdown.__name__ if saved_rdown else '<none>')
WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).
Active handler: _rdown_v3
Saved handler (rdown): _rdown_v2
[v3 DIAG] pixel=(749,402) world=(27.936,19.281)
[v3 DIAG] pixel=(597,440) world=(19.840,17.257)
[v3 DIAG] pixel=(442,432) world=(11.585,17.683)
[v3 DIAG] pixel=(449,326) world=(11.958,23.329)
6 — Live check
[v3 DIAG] messages — the diagnostic version is active.[6]:
print("Current action:", viewer.action)
print("→ Right-click in the viewer window to see [v3 DIAG]")
Current action: my demo
→ Right-click in the viewer window to see [v3 DIAG]
7 — Unregister: automatic restoration of v2
[7]:
viewer.unregister_action('my demo')
current = viewer._custom_rdown_handlers.get('my demo')
print("Active handler after unregister:",
current.__name__ if current else '<removed>')
print("_saved_handlers:", viewer._saved_handlers) # must be empty
# _rdown_v2 is active again — verifiable by restarting the action
viewer.start_action('my demo', 'Back to v2 — right-click to verify')
INFO unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.
INFO ACTION : Back to v2 — right-click to verify
Active handler after unregister: _rdown_v2
_saved_handlers: {}
[v2] snap=(21.864, 19.387)
[v2] snap=(14.780, 15.819)
[v2] snap=(10.093, 18.056)
8 — Cascaded overloads
overload=True call wins), so restoration always unwinds back[8]:
def _rdown_v4(v, ctx): print(f"[v4] X={ctx.x:.1f}")
def _rdown_v5(v, ctx): print(f"[v5] X={ctx.x:.1f}")
# v2 is active
viewer.register_action('my demo', rdown_handler=_rdown_v4, overload=True)
# → saves v2, installs v4
viewer.register_action('my demo', rdown_handler=_rdown_v5, overload=True)
# → slot 'rdown' already saved (v2); v4 is lost (WARNING, no additional save)
print("Active handler:",
viewer._custom_rdown_handlers.get('my demo').__name__)
saved_rdown = viewer._saved_handlers.get('my demo', {}).get('rdown')
print("Saved (rdown):",
saved_rdown.__name__ if saved_rdown else '<none>')
# Restoration: returns to v2 (not v4)
viewer.unregister_action('my demo')
current = viewer._custom_rdown_handlers.get('my demo')
print("After unregister:",
current.__name__ if current else '<removed>')
WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v2' saved and will be restored on unregister).
WARNING register_action: 'my demo' — rdown handler overloaded (previous '_rdown_v4' saved and will be restored on unregister).
INFO unregister_action: 'my demo' — rdown handler restored to '_rdown_v2'.
Active handler: _rdown_v5
Saved (rdown): _rdown_v2
After unregister: _rdown_v2
[v2] snap=(18.775, 17.151)
[v2] snap=(10.306, 16.884)
[v2] snap=(8.922, 20.399)
9 — Final cleanup
[ ]:
viewer.end_action('End overload demo')
viewer.unregister_action('my demo') # idempotent
print("Cleanup done.")
print("_custom_rdown_handlers:", viewer._custom_rdown_handlers)
print("_saved_handlers:", viewer._saved_handlers)
INFO ACTION : End overload demo
Cleanup done.
_custom_rdown_handlers: {}
_saved_handlers: {}
The Kernel crashed while executing code in the current cell or a previous cell.
Please review the code in the cell(s) to identify a possible cause of the failure.
Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info.
View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details.
API summary
# Initial registration (no conflict)
viewer.register_action('my action', rdown_handler=fn_v1)
# Permanent replacement — WARNING if slot already occupied, no save
viewer.register_action('my action', rdown_handler=fn_v2)
# Temporary overload — WARNING + saves the previous handler
viewer.register_action('my action', rdown_handler=fn_v3, overload=True)
# Unregister — restores fn_v2 if overload=True was used
viewer.unregister_action('my action')
Rules
The save happens only once per slot (first
overload=Truecall wins).A second
overload=Trueon the same slot emits aWARNINGbut does not replace the existing save → restoration always unwinds back to the handler that was present before the first overload.unregister_actionis idempotent: no-op if the action is unknown.overload=Falseon an occupied slot →WARNINGonly, previous handler permanently lost.