GL SDK's Plugin Architecture Implementation
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file gl_plugin-0.1.0.tar.gz.
File metadata
- Download URL: gl_plugin-0.1.0.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.8.24
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb4865bead1315c99945f57764ae70bc7acf08a95996604d17eaeebe29caf8b5
|
|
| MD5 |
1f9bd05e180262f6229a5ad04cd256e2
|
|
| BLAKE2b-256 |
a09c8210962d350b9eb27581edf3531fff66873c819fbf765b2aac9b6278b38f
|