Skip to content

Commit fab2139

Browse files
committed
add on_registration hook for plugin early setup; version bump to 0.0.67
1 parent 96708a6 commit fab2139

4 files changed

Lines changed: 265 additions & 1 deletion

File tree

scitrera_app_framework/api/plugins.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ def get_dependencies(self, v: Variables) -> Iterable[str] | None:
9090
"""
9191
return ()
9292

93+
# noinspection PyMethodMayBeStatic,PyUnusedLocal
94+
def on_registration(self, v: Variables) -> None:
95+
"""
96+
Optional hook called once when the plugin is first registered/collected,
97+
before initialization. This is called only once per plugin instance,
98+
regardless of how many times register_plugin is called.
99+
100+
Use this for early setup that needs to happen before initialization,
101+
such as registering additional plugins, setting up environment defaults,
102+
or other preparatory actions.
103+
104+
The default implementation does nothing.
105+
106+
:param v: the variables/environment instance
107+
"""
108+
pass
109+
93110
@abstractmethod
94111
def initialize(self, v: Variables, logger: Logger) -> object | None:
95112
"""

scitrera_app_framework/core/plugins.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ def register_plugin(plugin_type: Type[Plugin], v: Variables = None, init=False):
216216
name, ext_name, is_single, is_multi)
217217
pr[name] = instance
218218

219+
# call on_registration hook (only called once, on first registration)
220+
instance.on_registration(v)
221+
219222
# add to implementations registry to keep record of it
220223
_impl_options((ext_name := instance.extension_point_name(v)), v=v).add(instance)
221224

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="scitrera-app-framework",
8-
version="0.0.66",
8+
version="0.0.67",
99
author="Scitrera LLC",
1010
author_email="open-source-team@scitrera.com",
1111
description="Common Application Framework Code and Utilities",

tests/test_core_plugins.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,247 @@ def test_alternative_plugin_selection(self, clean_env):
412412
# With USE_ANOTHER=True, AnotherSimplePlugin should be selected
413413
result = get_extension("simple-ext", v)
414414
assert result["status"] == "another_initialized"
415+
416+
417+
class TestOnRegistration:
418+
"""Tests for on_registration hook."""
419+
420+
def test_on_registration_called(self, clean_env):
421+
"""Test that on_registration is called when plugin is registered."""
422+
from scitrera_app_framework import init_framework_test_harness
423+
from scitrera_app_framework.core.plugins import register_plugin
424+
425+
on_reg_calls = []
426+
427+
class OnRegPlugin(Plugin):
428+
def extension_point_name(self, v):
429+
return "onreg-ext"
430+
431+
def on_registration(self, v):
432+
on_reg_calls.append(("registered", v))
433+
434+
def initialize(self, v, logger):
435+
return "value"
436+
437+
v = init_framework_test_harness("test-app")
438+
register_plugin(OnRegPlugin, v, init=False)
439+
440+
assert len(on_reg_calls) == 1
441+
assert on_reg_calls[0][0] == "registered"
442+
assert on_reg_calls[0][1] is v
443+
444+
def test_on_registration_called_before_initialize(self, clean_env):
445+
"""Test that on_registration is called before initialize."""
446+
from scitrera_app_framework import init_framework_test_harness
447+
from scitrera_app_framework.core.plugins import register_plugin
448+
449+
call_order = []
450+
451+
class OrderTestPlugin(Plugin):
452+
def extension_point_name(self, v):
453+
return "order-ext"
454+
455+
def on_registration(self, v):
456+
call_order.append("on_registration")
457+
458+
def initialize(self, v, logger):
459+
call_order.append("initialize")
460+
return "value"
461+
462+
v = init_framework_test_harness("test-app")
463+
register_plugin(OrderTestPlugin, v, init=True)
464+
465+
assert call_order == ["on_registration", "initialize"]
466+
467+
def test_on_registration_not_called_twice(self, clean_env):
468+
"""Test that on_registration is only called once per plugin, not on duplicate registration."""
469+
from scitrera_app_framework import init_framework_test_harness
470+
from scitrera_app_framework.core.plugins import register_plugin
471+
472+
on_reg_calls = []
473+
474+
class SingleRegPlugin(Plugin):
475+
def extension_point_name(self, v):
476+
return "singlereg-ext"
477+
478+
def on_registration(self, v):
479+
on_reg_calls.append("called")
480+
481+
def initialize(self, v, logger):
482+
return "value"
483+
484+
v = init_framework_test_harness("test-app")
485+
486+
# Register multiple times
487+
register_plugin(SingleRegPlugin, v, init=False)
488+
register_plugin(SingleRegPlugin, v, init=False)
489+
register_plugin(SingleRegPlugin, v, init=True)
490+
491+
# on_registration should only be called once
492+
assert len(on_reg_calls) == 1
493+
494+
def test_on_registration_default_noop(self, clean_env):
495+
"""Test that plugins without on_registration override work fine (backwards compatibility)."""
496+
from scitrera_app_framework import init_framework_test_harness
497+
from scitrera_app_framework.core.plugins import register_plugin, get_extension
498+
499+
v = init_framework_test_harness("test-app")
500+
501+
# SimplePlugin doesn't override on_registration
502+
register_plugin(SimplePlugin, v, init=True)
503+
504+
# Should work normally
505+
result = get_extension("simple-ext", v)
506+
assert result["status"] == "initialized"
507+
508+
def test_on_registration_can_register_other_plugins(self, clean_env):
509+
"""Test that on_registration can register additional plugins."""
510+
from scitrera_app_framework import init_framework_test_harness
511+
from scitrera_app_framework.core.plugins import register_plugin, get_extension
512+
513+
class HelperPlugin(Plugin):
514+
def extension_point_name(self, v):
515+
return "helper-ext"
516+
517+
def initialize(self, v, logger):
518+
return "helper_value"
519+
520+
class MainPlugin(Plugin):
521+
def extension_point_name(self, v):
522+
return "main-ext"
523+
524+
def on_registration(self, v):
525+
# Register another plugin during on_registration
526+
register_plugin(HelperPlugin, v, init=False)
527+
528+
def initialize(self, v, logger):
529+
return "main_value"
530+
531+
v = init_framework_test_harness("test-app")
532+
register_plugin(MainPlugin, v, init=True)
533+
534+
# Both plugins should be accessible
535+
assert get_extension("main-ext", v) == "main_value"
536+
assert get_extension("helper-ext", v) == "helper_value"
537+
538+
def test_on_registration_can_set_variables(self, clean_env):
539+
"""Test that on_registration can modify the Variables instance."""
540+
from scitrera_app_framework import init_framework_test_harness
541+
from scitrera_app_framework.core.plugins import register_plugin, get_extension
542+
543+
class ConfigPlugin(Plugin):
544+
def extension_point_name(self, v):
545+
return "config-ext"
546+
547+
def on_registration(self, v):
548+
# Set some default configuration
549+
v.set("CONFIG_DEFAULT", "default_value")
550+
551+
def initialize(self, v, logger):
552+
return v.get("CONFIG_DEFAULT")
553+
554+
v = init_framework_test_harness("test-app")
555+
register_plugin(ConfigPlugin, v, init=True)
556+
557+
result = get_extension("config-ext", v)
558+
assert result == "default_value"
559+
560+
def test_on_registration_with_multi_extension(self, clean_env):
561+
"""Test that on_registration works with multi-extension plugins."""
562+
from scitrera_app_framework import init_framework_test_harness
563+
from scitrera_app_framework.core.plugins import register_plugin, get_extensions
564+
565+
on_reg_calls = []
566+
567+
class MultiRegPlugin1(Plugin):
568+
def extension_point_name(self, v):
569+
return "multireg-ext"
570+
571+
def is_multi_extension(self, v):
572+
return True
573+
574+
def on_registration(self, v):
575+
on_reg_calls.append("multi1")
576+
577+
def initialize(self, v, logger):
578+
return "multi1_value"
579+
580+
class MultiRegPlugin2(Plugin):
581+
def extension_point_name(self, v):
582+
return "multireg-ext"
583+
584+
def is_multi_extension(self, v):
585+
return True
586+
587+
def on_registration(self, v):
588+
on_reg_calls.append("multi2")
589+
590+
def initialize(self, v, logger):
591+
return "multi2_value"
592+
593+
v = init_framework_test_harness("test-app")
594+
register_plugin(MultiRegPlugin1, v, init=True)
595+
register_plugin(MultiRegPlugin2, v, init=True)
596+
597+
# Both on_registration hooks should be called
598+
assert on_reg_calls == ["multi1", "multi2"]
599+
600+
# Both extensions should work
601+
results = get_extensions("multireg-ext", v)
602+
assert len(results) == 2
603+
604+
def test_on_registration_with_disabled_plugin(self, clean_env):
605+
"""Test that on_registration is called even for disabled plugins."""
606+
from scitrera_app_framework import init_framework_test_harness
607+
from scitrera_app_framework.core.plugins import register_plugin
608+
609+
on_reg_calls = []
610+
611+
class DisabledRegPlugin(Plugin):
612+
def extension_point_name(self, v):
613+
return "disabledreg-ext"
614+
615+
def is_enabled(self, v):
616+
return False
617+
618+
def on_registration(self, v):
619+
on_reg_calls.append("disabled_registered")
620+
621+
def initialize(self, v, logger):
622+
return "should_not_init"
623+
624+
v = init_framework_test_harness("test-app")
625+
register_plugin(DisabledRegPlugin, v, init=True)
626+
627+
# on_registration should still be called even though plugin is disabled
628+
assert len(on_reg_calls) == 1
629+
assert on_reg_calls[0] == "disabled_registered"
630+
631+
def test_on_registration_with_lazy_plugin(self, clean_env):
632+
"""Test that on_registration is called immediately for lazy plugins."""
633+
from scitrera_app_framework import init_framework_test_harness
634+
from scitrera_app_framework.core.plugins import register_plugin
635+
636+
call_order = []
637+
638+
class LazyRegPlugin(Plugin):
639+
eager = False
640+
641+
def extension_point_name(self, v):
642+
return "lazyreg-ext"
643+
644+
def on_registration(self, v):
645+
call_order.append("on_registration")
646+
647+
def initialize(self, v, logger):
648+
call_order.append("initialize")
649+
return "lazy_value"
650+
651+
v = init_framework_test_harness("test-app")
652+
register_plugin(LazyRegPlugin, v, init=False)
653+
654+
# on_registration should be called immediately upon registration
655+
assert call_order == ["on_registration"]
656+
657+
# initialize should not be called yet (lazy)
658+
assert "initialize" not in call_order

0 commit comments

Comments
 (0)