Skip to main content

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 PluginManager for registering and managing plugins
  • Handler System: Abstract PluginHandler base class for defining plugin behavior and custom injections
  • Dependency Injection: Automatic service injection into plugins via type hints
  • Configuration Service: Built-in ConfigService for 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 synchronously
  • aregister_plugin(plugin_class, custom_initializer=None, additional_params=None) - Register a plugin asynchronously
  • get_plugin(name) - Get a plugin by name
  • get_plugins(handler_type=None, plugin_names=None) - Get all plugins, optionally filtered
  • get_handler(handler_type) - Get a handler by type
  • get_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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gl_plugin-0.1.0.tar.gz (12.9 kB view details)

Uploaded Source

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

Hashes for gl_plugin-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fb4865bead1315c99945f57764ae70bc7acf08a95996604d17eaeebe29caf8b5
MD5 1f9bd05e180262f6229a5ad04cd256e2
BLAKE2b-256 a09c8210962d350b9eb27581edf3531fff66873c819fbf765b2aac9b6278b38f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page