Stevedore (Python Plugin Manager)
Stevedore is a Python library designed to simplify the management of dynamic plugins for Python applications. It builds on top of `setuptools` entry points, providing a standardized and less repetitive way to discover and load extensions at runtime. The library abstracts away much of the boilerplate associated with `__import__` or `importlib` for plugin systems. [2, 5]
Warnings
- gotcha Be aware that another tool also named 'Stevedore' exists for Docker image building and container orchestration. Ensure you are using the correct Python plugin management library, often identified by its OpenStack origin. [1, 7, 8, 10, 12]
- gotcha Incorrectly defining entry points in `setup.py` or `pyproject.toml`, or referencing non-existent callables for your plugins, can lead to `ImportError` at runtime that is difficult to debug at packaging time. [15]
- gotcha While powerful, using a large number of `setuptools` entry points (and thus `stevedore` plugins) can potentially contribute to slower application startup times due to the discovery and loading process. [9]
- gotcha Stevedore's namespaces, while resembling Python package paths (e.g., `a.b.c`), do not directly correspond to Python package structures. They are logical identifiers for groups of plugins. [8]
- deprecated Support for older Python versions (e.g., Python 3.8 and earlier) has been dropped in recent Stevedore releases. [6]
Install
-
pip install stevedore
Imports
- DriverManager
from stevedore.driver import DriverManager
- ExtensionManager
from stevedore.extension import ExtensionManager
- NamedExtensionManager
from stevedore.named import NamedExtensionManager
- EnabledExtensionManager
from stevedore.enabled import EnabledExtensionManager
Quickstart
import os
from setuptools import setup, find_packages
from stevedore import extension
# 1. Define a plugin interface (e.g., in myapp/plugins/base.py)
# In a real scenario, this would be in a separate package.
# For this quickstart, we'll simulate it.
plugin_base_code = '''
import abc
class FormatterBase(abc.ABC):
@abc.abstractmethod
def format(self, data):
"""Format the data and return a string."""
pass
'''
# 2. Implement a concrete plugin (e.g., in myapp_simple_formatter/formatter.py)
# In a real scenario, this would be in a separate package.
plugin_impl_code = '''
from myapp.plugins.base import FormatterBase
class SimpleFormatter(FormatterBase):
def format(self, data):
return f"Simple format: {data['value']}"
'''
# Create dummy files for demonstration
if not os.path.exists('myapp/plugins'):
os.makedirs('myapp/plugins')
with open('myapp/plugins/base.py', 'w') as f:
f.write(plugin_base_code)
if not os.path.exists('myapp_simple_formatter'):
os.makedirs('myapp_simple_formatter')
with open('myapp_simple_formatter/formatter.py', 'w') as f:
f.write(plugin_impl_code)
with open('myapp/__init__.py', 'w') as f: pass
with open('myapp_simple_formatter/__init__.py', 'w') as f: pass
# 3. Define entry points in setup.py (or pyproject.toml)
# For a runnable quickstart, we'll use a dummy setup for the plugin
# and then manually register it to simulate installation.
# In a real project, myapp_simple_formatter would have its own setup.py
# and be installed via pip install -e .
# Simulate plugin registration by creating a pseudo-entry-point
# This is for quickstart demonstration; usually setuptools handles this via installation.
# This specific 'hack' for dynamic registration during runtime is not standard Stevedore usage,
# but it makes the quickstart runnable without a full package installation process.
# We'll use a hack to make the quickstart self-contained and runnable.
# In a real scenario, 'myapp_simple_formatter' would be an installed package
# with its entry point declared in its setup.py (or pyproject.toml).
# To simulate: we directly add the module to sys.modules and define a mock entry_points function.
import sys
sys.path.insert(0, os.path.abspath('.'))
# Temporarily import the base and plugin to make them available
from myapp.plugins.base import FormatterBase
from myapp_simple_formatter.formatter import SimpleFormatter
# Mock the entry point discovery for demonstration
def mock_entry_points(group=None):
if group == 'myapp.formatters':
class MockEntryPoint:
def __init__(self, name, load_callable):
self.name = name
self._load_callable = load_callable
def load(self):
return self._load_callable
return {
'simple': MockEntryPoint('simple', SimpleFormatter)
}
return {}
# Replace the real entry_points discovery for this script's scope
# This is a highly simplified mock for quickstart purposes and not how stevedore usually discovers plugins
# In practice, entry points are discovered via 'importlib.metadata.entry_points()' after package installation.
import stevedore.extension
stevedore.extension.entry_points = mock_entry_points
# 4. Use stevedore in your application to load plugins
def main():
print("Loading formatters...")
mgr = extension.ExtensionManager(
namespace='myapp.formatters',
invoke_on_load=True
)
if not mgr.extensions:
print("No formatters found. Ensure plugin packages are installed and define 'myapp.formatters' entry points.")
return
print(f"Found {len(mgr.extensions)} formatter(s).")
for ext in mgr.extensions:
print(f" - {ext.name}: {ext.obj.format({'value': 123})}")
if __name__ == '__main__':
main()
# Clean up dummy files/dirs
import shutil
shutil.rmtree('myapp')
shutil.rmtree('myapp_simple_formatter')