Plugin Foundation Documentation#
Overview#
The machine.py file forms the foundation of plugin functionality in the Jaseci Cloud architecture. It leverages the Pluggy library to define a flexible and modular plugin system using a hook mechanism.
This document provides a detailed breakdown of how plugins are structured and implemented in machine.py, including all relevant classes and static methods. It is designed to help contributors and maintainers understand this file independently, without needing a full view of the entire Jaseci codebase.
What is Pluggy?#
Pluggy is a lightweight plugin system library used extensively in projects like pytest. It allows defining:
- Hook specifications (
@hookspec) — method signatures to be implemented. - Hook implementations (
@hookimpl) — actual logic for the hooks. - Plugin Manager to register, manage, and dispatch hook calls dynamically.
Plugin Architecture class implementation#
Classes Implemented#
| Class | Role |
|---|---|
Spec |
Defines placeholder methods that plugin implementations must implement.For declaring the method interfaces. It doesn't do anything itself but tells pluggy what hooks are available. |
Impl |
For registering actual implementations using @hookimpl |
Proxy |
For calling plugin methods from the outside, through plugin_manager.hook. |
good example to understand the 3 classes and use of proxy class#
You're building a plugin-driven data pipeline framework. Each plugin can implement one or more of:
-
load_data(source: str) -> dict
-
transform_data(data: dict) -> dict
-
save_data(data: dict, target: str) -> None
We will:
-
Declare a hook spec
-
Implement two plugins (one for CSV, one for JSON)
-
Dynamically generate a proxy interface
-
Run a pipeline using the proxy
Lets implement the 2 plugins CSVPlugin and JSONPlugin
import pluggy
hookspec = pluggy.HookspecMarker("pipeline")
hookimpl = pluggy.HookimplMarker("pipeline")
class PipelineSpec:
@hookspec(firstresult=True)
def load_data(self, source: str) -> dict:
"""Loads data from a source."""
@hookspec
def transform_data(self, data: dict) -> dict:
"""Transforms the given data."""
@hookspec
def save_data(self, data: dict, target: str) -> None:
"""Saves data to the target."""
class CSVPlugin:
@hookimpl
def load_data(self, source: str) -> dict:
if source.endswith(".csv"):
print(f"Loading CSV from {source}")
return {"data": [1, 2, 3]}
return None
@hookimpl
def transform_data(self, data: dict) -> dict:
print("Transforming CSV data by squaring...")
data["data"] = [x * x for x in data["data"]]
return data
class JSONPlugin:
@hookimpl
def load_data(self, source: str) -> dict:
if source.endswith(".json"):
print(f"Loading JSON from {source}")
return {"data": [10, 20, 30]}
return None
@hookimpl
def save_data(self, data: dict, target: str) -> None:
print(f"Saving data to {target}: {data}")
Lets register the plugins and create a dynamic proxy that can help to decide what plugin method to implement
import pluggy
from specs import PipelineSpec, CSVPlugin, JSONPlugin
plugin_manager = pluggy.PluginManager("pipeline")
plugin_manager.add_hookspecs(PipelineSpec)
plugin_manager.register(CSVPlugin())
plugin_manager.register(JSONPlugin())
def make_proxy_method(name):
def proxy_method(self, *args, **kwargs):
return getattr(self.plugin_manager.hook, name)(*args, **kwargs)
return proxy_method
def generate_proxy_class(hook_names: list[str]):
methods = {name: make_proxy_method(name) for name in hook_names}
methods["__init__"] = lambda self, plugin_manager: setattr(self, "plugin_manager", plugin_manager)
return type("Proxy", (), methods)
hook_names = ["load_data", "transform_data", "save_data"]
Proxy = generate_proxy_class(hook_names)
Lets use the proxy to call the plugins methods adoptively
proxy = Proxy(plugin_manager)
# Step 1: Load data
source = "file.csv"
data = proxy.load_data(source)
# Step 2: Transform (all plugins get to contribute)
data = proxy.transform_data(data)
# Step 3: Save (only plugins with save_data do it)
proxy.save_data(data, "output.json")
Loading CSV from file.csv
Transforming CSV data by squaring...
Saving data to output.json: {'data': [1, 4, 9]}
What does JacmachineInterface class do#
This class is the core interface layer of the plugin system. It:
- Inherits and composes multiple core classes like
JacClassReferences,JacNode,JacEdge, etc. - Bridges access between static utility classes and plugin-enabled logic.
- Allows static access patterns for interacting with various components like walkers, access control, etc.
Inherited Static Classes#
JacClassReferencesJacAccessValidation: Static functions related to access/permission managment in Jac are implementedJacNode: Static functions related to nodes are implementedJacEdge: Static functions related to edges are implemented(only one function can be merged with JacNode)JacWalker: Missing documentation.JacBuiltin: Centralized reference holder for core Jaseci class and type aliases.JacCmd: Static functions related to cmd implementationJacBasics: Missing documentation.JacUtils: utility functions related to Jac are implemented
How to Implement a Plugin#
Step 1: Define an Implementation Method with @hookimpl#
Step 2: Register Your Plugin with the Plugin Manager#
Static Methods that can be plugged in#
The following static methods are exposed through the plugin interface and can be improved using hook implementation
| Function | Notes |
|---|---|
connect |
Needs better description — what kind of connection? What parameters? |
disconnect |
Clarify whether it's object-to-object disconnection or session-related. |
perm_grant |
Rename or clarify — it grants permission to everyone. |
perm_revoke |
Removes all permissions — state scope and cascading effects. |
check_read_access |
Widely used — should include examples and return behavior. |
check_write_access |
Add detailed context on when and why it’s triggered. |
check_connect_access |
Explain with use case and expected outcome. |
check_access_level |
Critical access logic — needs detailed breakdown. |
get_edges_with_node |
Clarify difference from get_edges. Add real-world usage scenarios. |
edges_to_nodes |
Misleading name — it means “get connected nodes”. Rename or explain. |
remove_edge |
Common function — include code samples or visual diagrams. |
visit / ignore |
Missing docstrings — clarify usage context and purpose. |
spawn_call |
Describe lifecycle and execution flow clearly. |
async_spawn_call |
Needs distinct async-specific explanation (currently same as spawn_call). |
disengage |
Explain context — e.g., walker termination, access cleanup, etc. |
setup |
Clarify what is being setup — graph, context, permissions, etc.? |
reset_graph |
Clarify whether it's a full or partial reset — and when to use it. |
make_archetype |
Add explanation about its role in object/class/type creation. |
impl_patch_filename |
Explain purpose — dynamic patching, hot reloading, etc. |
jac_import |
Key function — handles .jac and .py imports. Needs examples and context. |