Plux - A dynamic code loading framework for building plugable Python distributions

Related tags

Miscellaneousplux
Overview

Plux

CI badge PyPI Version PyPI License Code style: black

plux is the dynamic code loading framework used in LocalStack.

Overview

The plux builds a higher-level plugin mechanism around Python's entry point mechanism. It provides tools to load plugins from entry points at run time, and to discover entry points from plugins at build time (so you don't have to declare entry points statically in your setup.py).

Core concepts

  • PluginSpec: describes a Plugin. Each plugin has a namespace, a unique name in that namespace, and a PluginFactory (something that creates Plugin the spec is describing. In the simplest case, that can just be the Plugin's class).
  • Plugin: an object that exposes a should_load and load method. Note that it does not function as a domain object (it does not hold the plugins lifecycle state, like initialized, loaded, etc..., or other metadata of the Plugin)
  • PluginFinder: finds plugins, either at build time (by scanning the modules using pkgutil and setuptools) or at run time (reading entrypoints of the distribution using stevedore)
  • PluginManager: manages the run time lifecycle of a Plugin, which has three states:
    • resolved: the entrypoint pointing to the PluginSpec was imported and the PluginSpec instance was created
    • init: the PluginFactory of the PluginSpec was successfully invoked
    • loaded: the load method of the Plugin was successfully invoked

architecture

Loading Plugins

At run time, a PluginManager uses a PluginFinder that in turn uses stevedore to scan the available entrypoints for things that look like a PluginSpec. With PluginManager.load(name: str) or PluginManager.load_all(), plugins within the namespace that are discoverable in entrypoints can be loaded. If an error occurs at any state of the lifecycle, the PluginManager informs the PluginLifecycleListener about it, but continues operating.

Discovering entrypoints

At build time (e.g., with python setup.py develop/install/sdist), a special PluginFinder collects anything that can be interpreted as a PluginSpec, and creates from it setuptools entrypoints. In the setup.py we can use the plugin.setuptools.load_entry_points method to collect a dictionary for the entry_points value of setup().

from plugin.setuptools import load_entry_points

setup(
    entry_points=load_entry_points(exclude=("tests", "tests.*",))
)

Note that load_entry_points will try to resolve a cached version of entry_points.txt from the .egg-info directory, to avoid resolving the entry points when building the package from a source distribution.

Examples

To build something using the plugin framework, you will first want to introduce a Plugin that does something when it is loaded. And then, at runtime, you need a component that uses the PluginManager to get those plugins.

One class per plugin

This is the way we went with LocalstackCliPlugin. Every plugin class (e.g., ProCliPlugin) is essentially a singleton. This is easy, as the classes are discoverable as plugins. Simply create a Plugin class with a name and namespace and it will be discovered by the build time PluginFinder.

# abstract case (not discovered at build time, missing name)
class CliPlugin(Plugin):
    namespace = "my.plugins.cli"

    def load(self, cli):
        self.attach(cli)

    def attach(self, cli):
        raise NotImplementedError

# discovered at build time (has a namespace, name, and is a Plugin)
class MyCliPlugin(CliPlugin):
    name = "my"

    def attach(self, cli):
        # ... attach commands to cli object

now we need a PluginManager (which has a generic type) to load the plugins for us:

cli = # ... needs to come from somewhere

manager: PluginManager[CliPlugin] = PluginManager("my.plugins.cli", load_args=(cli,))

plugins: List[CliPlugin] = manager.load_all()

# todo: do stuff with the plugins, if you want/need
#  in this example, we simply use the plugin mechanism to run a one-shot function (attach) on a load argument

Re-usable plugins

When you have lots of plugins that are structured in a similar way, we may not want to create a separate Plugin class for each plugin. Instead we want to use the same Plugin class to do the same thing, but use several instances of it. The PluginFactory, and the fact that PluginSpec instances defined at module level are discoverable (inpired by pluggy), can be used to achieve that.

class ServicePlugin(Plugin):

    def __init__(self, service_name):
        self.service_name = service_name
        self.service = None

    def should_load(self):
        return self.service_name in config.SERVICES

    def load(self):
        module = importlib.import_module("localstack.services.%s" % self.service_name)
        # suppose we define a convention that each service module has a Service class, like moto's `Backend`
        self.service = module.Service()

def service_plugin_factory(name) -> PluginFactory:
    def create():
        return ServicePlugin(name)

    return create

# discoverable
s3 = PluginSpec("localstack.plugins.services", "s3", service_plugin_factory("s3"))

# discoverable
dynamodb = PluginSpec("localstack.plugins.services", "dynamodb", service_plugin_factory("dynamodb"))

# ... could be simplified with convenience framework code, but the principle will stay the same

Then we could use the PluginManager to build a Supervisor

class Supervisor:
    manager: PluginManager[ServicePlugin]

    def start(self, service_name):
        plugin = manager.load(service_name)
        service = plugin.service
        service.start()

Functions as plugins

with the @plugin decorator, you can expose functions as plugins. They will be wrapped by the framework into FunctionPlugin instances, which satisfy both the contract of a Plugin, and that of the function.

from plugin import plugin


@plugin(namespace="localstack.configurators")
def configure_logging(runtime):
    logging.basicConfig(level=runtime.config.loglevel)

    
@plugin(namespace="localstack.configurators")
def configure_somethingelse(runtime):
    # do other stuff with the runtime object
    pass

With a PluginManager via load_all, you receive the FunctionPlugin instances, that you can call like the functions

runtime = LocalstackRuntime()

for configurator in PluginManager("localstack.configurators").load_all():
    configurator(runtime)

Install

pip install plux

Develop

Create the virtual environment, install dependencies, and run tests

make venv
make test

Run the code formatter

make format

Upload the pypi package using twine

make upload
Owner
LocalStack
Enabling efficient local dev&test loops for Cloud applications
LocalStack
Islam - This is a simple python script.In this script I have written all the suras of Al Quran. As a result, by using this script, you can know the number of any sura at the moment.

Introduction: If you want to know sura number of al quran by just typing the name of sura than you can use this script. Usage in termux: $ pkg install

Fazle Rabbi 1 Jan 02, 2022
Coffeematcher is a python library to randomly match participants for coffee meetings.

coffeematcher coffeematcher is a python library to randomly match participants for coffee meetings. Installation Clone the repository: git clone https

Thomas Wesselink 3 May 06, 2022
A working roblox account generator it doesnt bypass the capcha stuff cuz these didnt showed up in my test runs

A working roblox account generator (state 11.5.2021) it doesnt bypass the capcha stuff cuz these didnt showed up in my test runs

TerrificTable 22 Jan 03, 2023
SmartGrid - Een poging tot een optimale SmartGrid oplossing, door Dirk Kuiper & Lars Zwaan

SmartGrid - Een poging tot een optimale SmartGrid oplossing, door Dirk Kuiper & Lars Zwaan

1 Jan 12, 2022
Expression interpreter written in Python

Calc Interpreter An interpreter modeled after a calculator implemented in Python 3. The program currently only supports basic mathematical expressions

1 Oct 17, 2021
This is a simple python script for checking A/L Examination results of srilankan students

AL-Result-Checker This is a simple python script for checking A/L Examination results of srilankan students INSTALLATION [Termux] [Linux] : apt-get up

Razor Kenway 8 Oct 24, 2022
SpellingBeeSolver - This program generates solutions to NYT style spelling bee problems.

SpellingBeeSolver This program generates solutions to NYT style spelling bee problems. The initial version of this program is being written in Python

1 Jan 01, 2022
Draw random mazes in python

a-maze Draw random mazes in python This program generates and draws a rectangular maze, with an entrance on one side and one on the opposite side. The

Andrea Pasquali 1 Nov 21, 2021
Ontario-Covid-Screening - An automated Covid-19 School Screening Tool for Ontario

Ontario-Covid19-Screening An automated Covid-19 School Screening Tool for Ontari

Rayan K 0 Feb 20, 2022
万能通用对象池,可以池化任意自定义类型的对象。

pip install universal_object_pool 此包能够将一切任意类型的python对象池化,是万能池,适用范围远大于单一用途的mysql连接池 http连接池等。 框架使用对象池包,自带实现了4个对象池。可以直接开箱用这四个对象池,也可以作为例子学习对象池用法。

12 Dec 15, 2022
Declarative and extensible library for configuration & code separation

ClassyConf ClassyConf is the configuration architecture solution for perfectionists with deadlines. It provides a declarative way to define settings f

83 Dec 07, 2022
Domoticz-hyundai-kia - Domoticz Hyundai-Kia plugin for Domoticz home automation system

Domoticz Hyundai-Kia plugin Author: Creasol https://www.creasol.it/domotics For

Creasol 7 Aug 03, 2022
System Information Utility With Python

System-Information-Utility This is a simple utility, for the terminal, which allows you to find out information about your PC. It's very easy to run t

2 Apr 15, 2022
KUIZ is a web application quiz where you can create/take a quiz for learning and sharing knowledge from various subjects, questions and answers.

KUIZ KUIZ is a web application quiz where you can create/take a quiz for learning and sharing knowledge from various subjects, questions and answers.

Thanatibordee Sihaboonthong 3 Sep 12, 2022
A Gura parser implementation for Python

Gura parser This repository contains the implementation of a Gura format parser in Python. Installation pip install gura-parser Usage import gura gur

JWare Solutions 19 Jan 25, 2022
The Playwright Workshop for TAU: The Homecoming

tau-playwright-workshop This repository contains the instructions and example code for the Playwright workshop for TAU: The Homecoming on December 1,

Pandy Knight 134 Dec 30, 2022
A lightweight and unlocked launcher for Lunar Client made in Python.

LCLPy LCL's Python Port of Lunar Client Lite. Releases: https://github.com/Aetopia/LCLPy/releases Build Install PyInstaller. pip install PyInstaller

21 Aug 03, 2022
A small script I made that takes any standard Decklist of magic the gathering cards and pulls all card images from scryfall at once!

A small script I made that takes any standard Decklist of magic the gathering cards and pulls all card images from scryfall at once!

15 Aug 26, 2022
An Notifier Program that Notifies you to relax your eyes Every 15 Minutes👀

Every 15 Minutes ⌛ Every 15 Minutes is an application that is used to Notify you to Relax your eyes Every 15 Minutes, This is fully made with Python a

FSP Gang s' YT 2 Oct 18, 2021
Like Docker, but for Squeak. You know, for kids.

Squeaker Like Docker, but for Smalltalk images. You know, for kids. It's a small program that helps in automated derivation of configured Smalltalk im

Tony Garnock-Jones 14 Sep 11, 2022