Getting Started
Introduction
Plugins are arbitrary callables. They can declare other plugins as requirements while operating under certain guarantees:
A plugin can be loaded (i.e. called) exactly once until it is unloaded.
A plugin’s dependencies will be loaded before.
A plugin’s loaded dependents will be reloaded after.
When a plugin is unloaded, its loaded dependents will be unloaded before.
This paradigm naturally puts an emphasis on the structure of packages applications and less on its orchestration. This allows consumers of applications to easily swap or add plugins while guaranteeing conformity to API contracts.
Install
The package is currently not available on pypi pending a PEP 541 request
The package can be configured as a Github dependency in a requirements.txt
pyplugin @ git+https://github.com/pyplugin/pyplugin@main
or to pin to a tag
pyplugin @ git+https://github.com/pyplugin/pyplugin@v0.1
Quickstart
This quickstart guide is intended to give you the bare bones needed to begin writing simple plugins. For thorough documentation, please see the User Guide.
Defining a Plugin
The fastest way to define a plugin is by using the built-in decorator:
from pyplugin import plugin
@plugin
def db_client(uri):
return db_library.connect(uri)
Loading a Plugin
You can load the plugin by simply calling it:
client = db_client()
or by explicitly calling the load() method:
client = db_client.load()
Plugin Requirements
We can define prerequisite plugins that will and must be loaded before loading:
from pyplugin import plugin
@plugin
def db_client(uri):
return db_library.connect(uri)
@plugin(requires="db_client")
def db_writer(db_client):
def func(doc):
return db_client.insert_one(doc)
return func
Note: Plugins are automatically named and registered under their fully-qualified dot-delimited package-name with
using __name__. To set the name, use the name argument.
Now to use db_writer, db_client must be loaded (or it will attempt to load). In addition, this
allows consumers of this library an opportunity to swap db_client with a custom user-defined implementation.
For example:
# User Code (Option 1)
from my_library import db_client
db_client.load(uri="mongodb://localhost:27018")
# User Code (Option 2)
from my_library import db_client
from pyplugin import plugin, register
@plugin
class DictDB(dict):
def insert_one(doc):
self[doc["_id"]] = doc
# Replace (Option 1)
replace_registered_plugin("db_client", DictDB)
# Replace (Option 2)
db_client.replace_with(DictDB)
Now whenever db_writer is used, it will use the new DictDB.
Unloading a Plugin
We can define an unload operation upon definition:
from pyplugin import plugin
@plugin(
unload_callable=lambda instance: instance.disconnect()
)
def db_client(uri):
return db_library.connect(uri)
Now if we call the unload() method, the unload_callable will be called.
Before a plugin is unloaded, any dependent plugins are unloaded first. For example, unloading db_client will
result in db_writer to be unloaded beforehand.
Loading the Plugin Again
Now, say we want to load db_client again with a different uri:
client = db_client("mongodb://localhost:27017")
client = db_client("mongodb://localhost:27018")
Unravelling the calls this will be equivalent to:
client = db_client.load("mongodb://localhost:27017", conflict_strategy="replace")
> client = db_library.connect("mongodb://localhost:27017")
client = db_client.load("mongodb://localhost:27018", conflict_strategy="replace")
> db_client.unload()
>> db_library.disconnect("mongodb://localhost:27017")
> client = db_library.connect("mongodb://localhost:27018")