GL Plugin
GL SDK's Plugin Architecture Implementation
A Python library that provides a robust plugin architecture with automatic dependency injection, handler-based plugin management, and environment configuration support.
Features
- Plugin Management: Thread-safe singleton
PluginManagerfor registering and managing plugins - Handler System: Abstract
PluginHandlerbase class for defining plugin behavior and custom injections - Dependency Injection: Automatic service injection into plugins via type hints
- Configuration Service: Built-in
ConfigServicefor environment-based configuration with type conversion - Async Support: Full support for both synchronous and asynchronous plugin initialization
Pre-requisites
- Python: 3.11 or higher (< 3.14), Python 3.13 is recommended.
- Package Manager: uv (recommended)
To install uv:
# Using pip
pip install uv
# Or using curl (macOS/Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Or using PowerShell (Windows)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Setting Up
1. Clone the Repository
git clone git@github.com:GDP-ADMIN/gl-sdk.git
cd libs/gl-plugin
2. Install Dependencies
uv sync
Installation
As a Dependency
uv add gl-plugin
For Development
git clone git@github.com:GDP-ADMIN/gl-sdk.git
cd libs/gl-plugin
uv sync
Quick Start
Step 1: Define a Handler
Handlers define what services are available for injection and how plugins are initialized.
from typing import Any, Dict, Type
from gl_plugin.plugin.handler import PluginHandler
class MyHandler(PluginHandler):
@classmethod
def create_injections(cls, instance: Any) -> Dict[Type, Any]:
return {}
@classmethod
def initialize_plugin(cls, instance: Any, plugin: Any) -> None:
pass
Step 2: Define a Plugin
Plugins must specify a handler using @Plugin.for_handler() and define required class attributes.
from gl_plugin.plugin.plugin import Plugin
@Plugin.for_handler(MyHandler)
class GreetingPlugin(Plugin):
name = "greeting_plugin"
description = "A greeting plugin"
version = "1.0.0"
def greet(self, name: str) -> str:
return f"Hello, {name}!"
Step 3: Register and Use
from gl_plugin.plugin.manager import PluginManager
manager = PluginManager(handlers=[MyHandler()])
manager.register_plugin(GreetingPlugin)
plugin = manager.get_plugin("greeting_plugin")
print(plugin.greet("World")) # Hello, World!
API Reference
PluginManager
The central manager for plugin lifecycle and service injection.
manager = PluginManager(
handlers=[...], # List of PluginHandler instances (required)
global_services=[...], # Optional list of services available to all handlers
)
Methods:
register_plugin(plugin_class, custom_initializer=None, additional_params=None)- Register a plugin synchronouslyaregister_plugin(plugin_class, custom_initializer=None, additional_params=None)- Register a plugin asynchronouslyget_plugin(name)- Get a plugin by nameget_plugins(handler_type=None, plugin_names=None)- Get all plugins, optionally filteredget_handler(handler_type)- Get a handler by typeget_handlers(handler_type=None)- Get all handlers, optionally filtered
Plugin
Base class for all plugins.
@Plugin.for_handler(MyHandler)
class MyPlugin(Plugin):
name = "my_plugin" # Required
description = "Description" # Required
version = "1.0.0" # Required
my_service: MyService # Auto-injected based on type hint
PluginHandler
Abstract base class for defining plugin handlers.
class MyHandler(PluginHandler):
@classmethod
def create_injections(cls, instance) -> Dict[Type, Any]:
"""Return services to be injected."""
return {ServiceType: service_instance}
@classmethod
def initialize_plugin(cls, instance, plugin) -> None:
"""Initialize plugin after creation."""
pass
@classmethod
async def ainitialize_plugin(cls, instance, plugin) -> None:
"""Async initialization (optional)."""
pass
ConfigService
Built-in service for environment configuration. The PluginManager automatically loads .env files from the current directory (searching up to 3 levels).
config: ConfigService
config.get_string("KEY", default="default")
config.get_int("KEY", default=0)
config.get_float("KEY", default=0.0)
config.get_bool("KEY", default=False) # true/yes/1/on = True
config.get_list("KEY", separator=",", default=[])
# Required values (raises ValueError if not set)
config.require("KEY")
config.require_as("KEY", int)
Project Structure
gl-plugin/
├── gl_plugin/
│ ├── __init__.py
│ ├── plugin/
│ │ ├── manager.py # PluginManager singleton
│ │ ├── plugin.py # Plugin base class with DI
│ │ ├── handler.py # PluginHandler abstract base
│ │ └── registry.py # ServiceRegistry for DI
│ └── services/
│ └── config.py # ConfigService
├── tests/
│ └── unit_tests/
├── pyproject.toml
└── uv.lock
Development Commands
uv sync # Install dependencies
uv run ruff check . # Run linter
uv run ruff check --fix . # Auto-fix lint issues
uv run ruff format . # Format code
uv run mypy gl_plugin/ # Type checking
uv run pytest # Run all tests
uv run pytest --cov=gl_plugin --cov-report=term-missing # Tests with coverage
Examples
Complete Example
Create a main.py file:
"""Example demonstrating multiple plugins."""
from typing import Any, Dict, Type
from gl_plugin.plugin.handler import PluginHandler
from gl_plugin.plugin.manager import PluginManager
from gl_plugin.plugin.plugin import Plugin
class MathHandler(PluginHandler):
@classmethod
def create_injections(cls, instance: Any) -> Dict[Type, Any]:
return {}
@classmethod
def initialize_plugin(cls, instance: Any, plugin: Any) -> None:
print(f"Registered: {plugin.name}")
@Plugin.for_handler(MathHandler)
class AddPlugin(Plugin):
name = "add"
description = "Adds two numbers"
version = "1.0.0"
def compute(self, a: int, b: int) -> int:
return a + b
@Plugin.for_handler(MathHandler)
class SubtractPlugin(Plugin):
name = "subtract"
description = "Subtracts two numbers"
version = "1.0.0"
def compute(self, a: int, b: int) -> int:
return a - b
def main():
manager = PluginManager(handlers=[MathHandler()])
manager.register_plugin(AddPlugin)
manager.register_plugin(SubtractPlugin)
add = manager.get_plugin("add")
subtract = manager.get_plugin("subtract")
print(f"5 + 3 = {add.compute(5, 3)}")
print(f"5 - 3 = {subtract.compute(5, 3)}")
if __name__ == "__main__":
main()
Run the example:
uv run python main.py
Expected output:
Registered: add
Registered: subtract
5 + 3 = 8
5 - 3 = 2
Async Plugin Example
import asyncio
from typing import Any, Dict, Type
from gl_plugin.plugin.handler import PluginHandler
from gl_plugin.plugin.manager import PluginManager
from gl_plugin.plugin.plugin import Plugin
class AsyncService:
async def fetch_data(self) -> str:
await asyncio.sleep(0.1)
return "Data fetched!"
class AsyncHandler(PluginHandler):
@classmethod
def create_injections(cls, instance: Any) -> Dict[Type, Any]:
return {AsyncService: AsyncService()}
@classmethod
def initialize_plugin(cls, instance: Any, plugin: Any) -> None:
pass
@classmethod
async def ainitialize_plugin(cls, instance: Any, plugin: Any) -> None:
print(f"Async initializing: {plugin.name}")
@Plugin.for_handler(AsyncHandler)
class AsyncPlugin(Plugin):
name = "async_plugin"
description = "A plugin with async support"
version = "1.0.0"
async_service: AsyncService
async def main():
manager = PluginManager(handlers=[AsyncHandler()])
await manager.aregister_plugin(AsyncPlugin)
plugin = manager.get_plugin("async_plugin")
result = await plugin.async_service.fetch_data()
print(result)
if __name__ == "__main__":
asyncio.run(main())
License
Copyright © 2025 GDP Labs
Metadata
Release files for gl-plugin 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gl_plugin-0.1.0.tar.gz | 12.9 kB | Details |
Release files / gl_plugin-0.1.0.tar.gz
| Download URL | gl_plugin-0.1.0.tar.gz |
|---|---|
| Size | 12.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fb4865bead1315c99945f57764ae70bc7acf08a95996604d17eaeebe29caf8b5
|
|
BLAKE2b-256 checksum How to use checksums |
a09c8210962d350b9eb27581edf3531fff66873c819fbf765b2aac9b6278b38f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.8.24
|