Plugin Handlers
Handlers
What is a Handler?
A Handler is the bridge between the Plugin Manager and your Plugins. It defines how a category of plugins should behave—what services they receive and what happens when they're initialized.
Think of it this way:
The Manager orchestrates everything
The Handler defines the rules for a plugin category
The Plugins follow those rules and implement functionality
Every plugin must be attached to a handler. The handler is responsible for:
Injecting services — Providing dependencies your plugins need
Initialization logic — Running setup code when a plugin is registered
Anatomy of a Handler
To create a handler, extend PluginHandler and implement two required class methods:
from gl_plugin.plugin.handler import PluginHandler
from typing import Any, Dict
class MyHandler(PluginHandler):
def __init__(self) -> None:
super().__init__()
@classmethod
def create_injections(cls, instance: Any) -> Dict[str, Any]:
# Define what services to inject into plugins
return {}
@classmethod
def initialize_plugin(cls, instance: Any, plugin: Any) -> None:
# Run setup logic after a plugin is instantiated
passcreate_injections(cls, instance)
create_injections(cls, instance)This method returns a dictionary of services that will be injected into every plugin attached to this handler. The injection happens automatically—plugins receive these services as instance attributes.
Parameters:
instance— The handler instance (useful for accessing handler properties)
Returns:
A dictionary mapping types to service instances or factories
initialize_plugin(cls, instance, plugin)
initialize_plugin(cls, instance, plugin)This method runs after a plugin is instantiated and its services are injected. Use it for any setup logic that requires the fully-constructed plugin.
Parameters:
instance— The handler instanceplugin— The newly created plugin instance
Handler Properties
Handlers can define their own properties to configure behavior. These properties are accessible in both create_injections() and initialize_plugin() via the instance parameter.
Example: Building a Calculator Handler
Let's build a handler that provides a CalculatorService to its plugins.
Create the Handler
Now create a handler that injects this service:
What's happening here:
create_injections()returns a dictionary withCalculatorServiceas the key and an instance as the valueSince
CalculatorServiceis stateless, all plugins can safely share the same instanceinitialize_plugin()passes the handler'sprecisionconfig to each plugin
Create the Concrete Implementations
Now let's create the concrete implementations. For the purposes of this example, we shall only create the ones defined within the CalculatorService; you are definitely allowed and welcome to create your own implementation and flows:
Notice that:
MathPlugindefines the abstractcalculate()method and receives the injectedCalculatorServiceConcrete plugins inherit both the service injection and the contract
Each implementation uses the same
calculatorservice differently
Instance vs Factory Injection
In create_injections(), you can return either a direct instance or a factory function. The choice depends on whether plugins need isolated or shared services.
Shared Instance (Direct)
The service is instantiated once when create_injections() is called. All plugins receive the same instance.
Use when:
The service is stateless (like our
CalculatorService)You want shared state across plugins (e.g., a shared cache)
The service doesn't hold plugin-specific data
Per-Plugin Instance (Factory)
The factory function is called each time a plugin is instantiated. Every plugin receives its own instance.
Use when:
The service holds plugin-specific state
Plugins would conflict if sharing (e.g., routers with overlapping routes)
Each plugin needs isolation
Example: Why Routers Need Factories
Consider an HTTP handler where plugins define their own routes:
If we used Router: Router() instead, both plugins share the same Router instance:
GithubPluginregistersPOST /webhook→ handler:github_webhook_handlerGooglePluginregistersPOST /webhook→ handler:google_webhook_handlerThe second registration overwrites the first in the shared Router
Even if the HttpHandler correctly prefixes the paths (/github/webhook and /google/webhook), both routes now point to google_webhook_handler because the handler function was overwritten in the shared Router.
With lambda: Router(), each plugin gets its own Router instance:
GithubPlugin→ its own Router →POST /webhook→github_webhook_handlerGooglePlugin→ its own Router →POST /webhook→google_webhook_handlerEach route correctly maps to its own handler function
Quick Reference
When in doubt, use a factory (lambda). Shared instances can cause subtle bugs that are difficult to trace when plugins unexpectedly affect each other's state.
Service: Service()
Once
Yes
Stateless utilities, shared caches
Service: lambda: Service()
Per plugin
No
Routers, plugin-specific state
Summary
__init__
Configure handler properties
When handler is created
create_injections()
Define services to inject
During plugin instantiation
initialize_plugin()
Post-injection setup
After plugin is fully created
Last updated