Skip to main content

Is a simple plugin manager that will dynamically load plugins from a directory given a config or env variable with dynamic kwargs to pass for plugin loading.

Project description

plugo

plugo is a simple plugin manager that dynamically loads plugins from a directory, a json configuration file e.g.plugins_config.json, an environment variable ENABLED_PLUGINS, or a predefined list (PLUGINS). It allows for dynamic keyword arguments (kwargs) to be passed during plugin loading, making it flexible for various applications like Flask

current_version = "v1.0.0"

Quickstart

Install

pip install plugo

Create a new plugin

Plugins will be created relative to the path you run the commands from.

Base Plugin

plugo new-base-plugin

Flask HTML Plugin

plugo new-ui-plugin

Flask RESTX API Plugin

plugo new-api-plugin

Optional Parameters

  • --name: Name of the Plugin. This will default the Cookiecutter answer
  • --output-dir: Relative path for output directory for the new plugin. Defaults to ./api/plugins.
Example Creation with Optional Parameters
plugo new-base-plugin --name="Example Plugin" --output-dir="plugins"

Example Plugin

Plugin Structure

All plugins have the following files:

  • metadata.json (Required)
  • plugin.py (Required)
  • requirements.txt (Optional)
└── 📁sample_plugin
    └── __init__.py
    └── metadata.json
    └── plugin.py
    └── requirements.txt

plugin.py Example

The plugin.py must have a init_plugin function defined in it with any optional named kwargs (key word arguments) that can be referenced or passed in as context later.

# plugin.py
from flask import Blueprint

plugin_blueprint = Blueprint('sample_plugin', __name__, template_folder='templates', static_folder='static')

@plugin_blueprint.route('/sample_plugin')
def plugin_route():
    return "Hello from sample_plugin!"


def init_plugin(app):
    app.register_blueprint(plugin_blueprint, url_prefix='/plugins')

metadata.json Example

The metadata.json defines metadata about the plugin. A core consideration is plugin dependencies—a list of plugins in the same directory that are required to load before this plugin can load.

// metadata.json

{
    "name": "sample_plugin",
    "version": "1.0.0",
    "description": "A sample plugin",
    "identifier": "com.example.sample_plugin",
    "dependencies": [
        "test_env_plugin"
    ],
    "author": "Your Name",
    "core_version": ">=1.0.0"
}

Example Project

Project Structure
└── 📁flask_base_plugins
    └── 📁plugins
        └── 📁sample_plugin
            └── __init__.py
            └── metadata.json
            └── plugin.py
            └── requirements.txt
        └── 📁test_env_plugin
            └── __init__.py
            └── metadata.json
            └── plugin.py
            └── requirements.txt
        └── __init__.py
    └── __init__.py
    └── app.py
    └── plugins_config.json
Loading Plugins

Plugins can be loaded from a plugins_config.json file or a comma separated list Environment Variable ENABLED_PLUGINS. The major difference is the level of control. The Environment Variable will assume all plugins in the list are active, while the plugins_config.json file allows you to specify if a plugin is active or not e.g.:

// plugins_config.json

{
    "plugins": [
        {
            "name": "sample_plugin",
            "enabled": true
        },
        {
            "name": "another_plugin",
            "enabled": false
        }
    ]
}
Using the Plugo Plugin Manager

You can load your plugins with the load_plugins function by importing it into your project:

from plugo.services.plugin_manager import load_plugins

The load_plugins function takes the following parameters:

  • plugin_directory (Optional): The path to the directory containing plugin folders.
  • config_path (Optional): The path to the plugin configuration JSON file.
  • logger (Optional): A logging.Logger instance for logging.
  • **kwargs (Optional): Additional keyword arguments passed to each plugin's init_plugin function (e.g., app for Flask applications).
Extended Functionality
  • The Environment Variable (ENABLED_PLUGINS): Load plugins specified in a comma-separated list in the ENABLED_PLUGINS environment variable.
  • The Predefined PLUGINS List variable: Allows you to Load plugins defined in a PLUGINS list variable using ImportClassDetails and PluginConfig.
Defining Plugins with ImportClassDetails and PluginConfig

You can define plugins programmatically using ImportClassDetails and PluginConfig and PLUGINS.

from plugo.models.import_class import ImportClassDetails
from plugo.models.plugin_config import PluginConfig, PLUGINS

Data Classes

  • ImportClassDetails: Specifies the module path and class or function name to import.
  • PluginConfig: Holds the configuration for a plugin, including its name, import details, and status.
  • PLUGINS: A Singleton list, used to store PluginConfig instances for programmatic plugin loading.
Defining Plugins Programmatically

By using ImportClassDetails and PluginConfig, you have full control over how plugins are loaded in your application. This method allows you to specify plugins that might not be located in the default plugin directory or to programmatically activate or deactivate plugins based on certain conditions.

Example app.py:

# app.py

import os

from flask import Flask

from plugo.models.import_class import ImportClassDetails
from plugo.models.plugin_config import PluginConfig, PLUGINS
from plugo.services.consolidate_plugin_requirements import (
    consolidate_plugin_requirements,
)
from plugo.services.plugin_manager import load_plugins

app = Flask(__name__)

# Initialize your app configurations, database, etc.

# Paths (Optional if using plugin_directory and config_path)
plugin_directory = os.path.join(app.root_path, "plugins")
plugin_config_path = os.path.join(app.root_path, "plugins_config.json")

# Create a PluginConfig instance with the plugin's name, import details, and status
plugin_config = PluginConfig(
    plugin_name="test_env_plugin",
    # Create an ImportClassDetails instance specifying the module and class/function to import
    import_class_details=ImportClassDetails(
        module_path="plugo.examples.flask_base_plugins.plugins.test_env_plugin.plugin",
        module_class_name="init_plugin",
    ),
    status="active",
)

# Add the PluginConfig instance to the PLUGINS list
PLUGINS.append(plugin_config)

# Set Environment Variable for Plugins (Optional)
os.environ["ENABLED_PLUGINS"] = "SomeOtherPlugin"

# Load plugins based on the configuration
loaded_plugins = load_plugins(
    plugin_directory=plugin_directory,  # Optional
    config_path=plugin_config_path,  # Optional
    logger=None,  # Optional
    app=app,  # kwargs passed to init_plugin
)

# Create Dynamic requirements-plugins.txt for deployments
consolidate_plugin_requirements(
    plugin_directory=plugin_directory,
    loaded_plugins=loaded_plugins,
)


if __name__ == "__main__":
    app.run(debug=True)
Explanation
  • Import Statements: Import necessary modules and classes from plugo and Flask.
  • App Initialization: Create a Flask app instance.
  • Logging Setup: Configure logging for better visibility (optional). In our example we are using the default logger set up in the function.
  • Paths: Define plugin_directory and plugin_config_path (optional if not using directory or config file).
  • Define Programmatic Plugins: Use PluginConfig and ImportClassDetails to define plugins programmatically.
    • ImportClassDetails: Specify the module path and class/function name for the plugin.
    • PluginConfig: Create a configuration for the plugin, including its name, module and class details and status.
    • Add to PLUGINS: Append the PluginConfig instance to the PLUGINS list.
  • Environment Variable: Set ENABLED_PLUGINS to load plugins specified in the environment (optional assumed to be active if set and found in the plugin directory).
  • Load Plugins: Call load_plugins with the appropriate parameters.
    • If plugin_directory and config_path are not provided, the function relies on ENABLED_PLUGINS and PLUGINS.
  • Loaded Plugins: Print the set of loaded plugins for verification.
  • Run the App: Start the Flask application.
Consolidating Plugin Requirements

You can optionally consolidate custom requirements from plugins using the consolidate_plugin_requirements function:

from plugo.services.consolidate_plugin_requirements import consolidate_plugin_requirements

The intent of this function is to support deployments and allow only what is required to be installed into your deployment environment especially if you have multiple plugins for different clients. This function takes the following parameters:

  • plugin_directory (Required): The directory where plugins are stored.
  • loaded_plugins (Required): List of plugin names that were loaded (This is the output of the load_plugins function).
  • logger (Optional): Logger instance for logging messages.
  • output_file (Optional): The output file to write the consolidated requirements to. Defaults to requirements-plugins.txt
Create a Plugin in the Dependent Project

In your dependent project, define a new command and register it using the entry points in pyproject.toml.

Example Plugin Command (hello_world.py):

import click

@click.command()
def hello_world():
    """Say Hello, World!"""
    click.echo("Hello, World!")

Register the Plugin in pyproject.toml: Assuming my_project is you project and package name:

[tool.poetry.plugins."plugo.commands"]
"hello_world" = "my_project.hello_world:hello_world"

Reinstall the Project

poetry lock
poetry install

Verify the Extended CLI After installing both plugo and the dependent project:

plugo --help

which should show:

Usage: plugo [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  new-base-plugin
  new-api-plugin
  new-ui-plugin
  hello-world  Say Hello, World!

Development

Test

pytest
coverage run -m pytest
coverage report
coverage html
mypy --html-report mypy_report .
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --format=html --htmldir="flake8_report/basic" --exclude=venv
flake8 . --count --exit-zero --max-complexity=11 --max-line-length=127 --statistics --format=html --htmldir="flake8_report/complexity" --exclude=venv

BumpVer

With the CLI command bumpver, you can search for and update version strings in your project files. It has a flexible pattern syntax to support many version schemes (SemVer, CalVer or otherwise). Run BumbVer with:

bumpver update --major
bumpver update --minor
bumpver update --patch

Build

poetry build

Publish

poetry publish

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

plugo-1.0.0.tar.gz (28.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plugo-1.0.0-py3-none-any.whl (44.4 kB view details)

Uploaded Python 3

File details

Details for the file plugo-1.0.0.tar.gz.

File metadata

  • Download URL: plugo-1.0.0.tar.gz
  • Upload date:
  • Size: 28.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.12.2 Windows/11

File hashes

Hashes for plugo-1.0.0.tar.gz
Algorithm Hash digest
SHA256 8c0c174457fa6794957d99e3104464e304d5ae05582d84b3944149304d1d58a2
MD5 127f125c26d8c94ca0bff529de25f0b8
BLAKE2b-256 3e1c0c73974039546611a8f78f8bee4181b051c1f804fc757c137e639295a7f9

See more details on using hashes here.

File details

Details for the file plugo-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: plugo-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 44.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.4 CPython/3.12.2 Windows/11

File hashes

Hashes for plugo-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a7a9c6cf4227337db8d5f2b0c58f47e10b76007ff49ba5c7dad5e203d35487e5
MD5 4df54afdb12fae1e62a6e72049f538d3
BLAKE2b-256 5d0d46b7510ccf8e011bce72b3420ad5edb3224d91ec7184b8a510f3c0ce01fe

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