Setting up a Plugin system

Setting up a plugin system is dead simple.

First create an instance of the System class

from pyitect import System
system = System()

The system class constructor takes two arguments, a configuration mapping and a yaml flag

The config mapping allows you to provide default requirements for components so if a call is made to system.load() with no requirements of it’s own the requirements from the passed config are used.

The yaml flag of course enables yaml support for the plugin system. allowing configuration file to be written in yaml. yaml support is not enabled by default because it requires the PyYAML library.

Next add some plugins to the system.

This can be done either by using the system.search() function to recursively search a directory for plugins.

Or added manualy by providing the path to a plugin folder to the system.add_plugin() function of your system instance.

system.search("path/to/your/plugins/tree")
system.add_plugin("paht/to/a/plugin/folder")

Now that you have some plugin you still have to enable them.

Enabling a plugin maps out the components it provides and make them available for loading by the plugin system. Tt does not load the plugin module or package unless there is an ‘on_enable` property in its configuration.

In which case, after the component are mapped and the plugin is enabled, the plugin module or package is loaded and an attempt is made to follow the path given to the on_enable configuration property to a callable object (ie. function) from the top level of the module or package and it is called passing only the Plugin configuration object for the plugin.

To enable a plugin you needs it’s Plugin instance. these can be accessed from system.plugins

a simple way to get a list of them would be.

plugins = [system.plugins[n][v] for n in system.plugins for v in system.plugins[n]]

After you have your list of Plugin objects you can filer it how you want to enable only the plugins you want to. When your ready.

system.enable_plugins(plugins)
enable_plugins can take multiple objects and any individual
can by a iterable or map of Plugin objects.

After you have some plugins enabled loading a provided component is as easy as

Bar = system.load("Bar")

The general idea is to create a system, search some path or paths for plugins and then enable them.

A plugin system can not be created without first creating an instance of the System class.

Global System

If you dont want to manage your plugin system instance yourself it is possible to have the pyitect module manage your plugin system for you. Simply use the pyitect.build_system() function to construct your plugin system inside pyitect. To later fetch your plugin system instance use pyitect.get_system(). To clean up and remove the existing system use pyitect.destroy_system().

‘on_enable’ Property

plugins can specify an on_enable property in their configuration. This is a doted name path to a function that is is executed right after a plugin is enabled and its components have been mapped. This allows for special cases where enabling a plugin requires more than just making it’s components available to be imported. For example is there is some system setup to be done.

pyitect.build_system(config, enable_yaml=False)
system = pyitect.get_system()
# ... do stuff
# end program / need fresh system?
pyitect.destroy_system()

Loading Components at run-time

Components are loaded at runtime via the system.load method or the system.load_component method

the second method requires you to explicitly state a plugin a version to load from this is not a common use can and is intended mostly for use with system.iter_component_providers.

The first method only needs the name of a component and will load any matching provider even if it is a subtype. This is the most common usecase

# if "a" is not avalieable will also load a "a.b" or "a.c"
a = system.load("a")

If the subtype matching is undesirable then it can be explicitly prevented with a key word argument

# will only load a "a" not a "a.b"
a = system.load("a", subs=False)

The default mode for selecting form among subtype is to sort alphanumerically and pick the first one. This is often not a desirable behavior in more complex situations. As such a key peramiter can be used

def key(prov):
    return (0 if prov[0] == "a.b" else 1)

# results in the loading of an "a.b"
a = system.load("a", key=key)

your key function cna be as complex or as simple as you want. they are sorting the results of a call to system.iter_component_providers. which yeilds tuples that look like (<component_name>, <plugin_name>, <version>)

if nessaccery there is a reverse kework perams to reverse the results of the sort

# results in the load of the logest and highest subtype
a = system.load("a", reverse=True)

Loading Plugins

Plugins are loaded on demand when a component is loaded via

system.load("<component name>")

a plugin can also be explicitly loaded via

system.load_plugin(plugin, version)

where plugin is the plugin name and version is the version

Tracking loaded Components

Pyitect tracks used components at anytime system.using can be inspected to find all components that have been requested and from what plugins they have been loaded along with versions.

system.using is a list of component.key() s

>>> system.using
{
    'component1' : {
        'plugin1`: ['1.0.2']
    },
    'special_component1' : {
        'special_plugin1': ['0.1.3'],
        'special_plugin2': ['0.2.4', '1.0.1-pre3']
    }
}

Pyitect also tracks enabled plugins system.enabeled_plugins is a mapping of plugin names to a mapping of versions to Plugin objects.

Like so

>>> system.enabeled_plugins
{
    "special_plugin1" : {
        "Version('1.0.0')": Plugin('special_plugin1:1.0.0')
    }
}