Plugin discovery¶
Binoc uses Python entry points for plugin discovery. This is the same mechanism for Python-only plugins and for native Rust plugins packaged as Python extension modules — the entry point's value distinguishes which loader to use.
For the rationale (why Python owns discovery and Rust owns execution, and why not pluggy / dynamic libraries / WASM), see Plugin discovery ADR. For the conceptual overview, see Plugin model.
The entry point group¶
Every binoc plugin is registered under the
binoc.plugins entry point group:
At startup, the binoc CLI (or any binoc host, such as
binoc.diff() from Python) calls
importlib.metadata.entry_points(group="binoc.plugins") and iterates
the result.
The <plugin-key> is a free string used only for display and
deduplication; by convention it matches the package name (for
example biobinoc, binoc-sqlite). The <target> distinguishes the
plugin type.
Two entry point shapes¶
Python plugins — module:function¶
Pure-Python plugins register a callable that is invoked with the
PluginRegistry:
# biobinoc/__init__.py
def register(registry):
from biobinoc.fasta import FastaComparator
from biobinoc.normalizer import SequenceNormalizer
registry.register_comparator("biobinoc.fasta", FastaComparator())
registry.register_transformer("biobinoc.sequence_normalizer", SequenceNormalizer())
The register(registry) function is called once at startup. Registered
plugin instances are held alive for the duration of the process.
Rust (native) plugins — bare module name¶
Rust plugins packaged as a maturin-built Python extension module
register the module name only, no :function:
Discovery detects the missing :function suffix, loads the native
shared library (the .so, .dylib, or .pyd installed alongside
the module) via libloading, reads the plugin descriptor, and
registers a NativeComparator / NativeTransformer / NativeRenderer
wrapper for each exported plugin. Per-file dispatch then goes through
the C ABI with no Python involvement.
The native side is generated by the export_plugin! macro in
binoc-sdk:
binoc_sdk::export_plugin! {
module: biobinoc,
comparators: [FastaComparator],
transformers: [SequenceNormalizer],
}
See Plugin SDK and ABI ADR for the C ABI contract.
Registry API¶
Both shapes ultimately call methods on the PluginRegistry:
| Method | Purpose |
|---|---|
register_comparator(name, comparator) |
Register a comparator by name. |
register_transformer(name, transformer) |
Register a transformer by name. |
register_renderer(name, renderer) |
Register a renderer by name. |
Names are the opaque strings referenced from dataset config
(comparators: [biobinoc.fasta]). They should be namespaced by
package (see Naming conventions below).
For ad-hoc / scripting / notebook use you can skip
entry-point registration entirely and attach plugin instances to a
Config directly. Ad-hoc comparators are inserted before the built-in
binary fallback:
config = binoc.Config.default()
config.add_comparator(FastaComparator())
config.add_transformer(SequenceNormalizer())
changeset = binoc.diff("snapshot-a", "snapshot-b", config=config)
Naming conventions¶
To prevent collisions across plugin packs:
| Thing | Convention | Examples |
|---|---|---|
| PyPI package name | binoc-* is the shared ecosystem namespace |
binoc-sqlite, binoc-html |
| Plugin names | package.name |
biobinoc.fasta, binoc-sqlite.sqlite |
| Tags | package.tag-name |
biobinoc.sequence-changed, binoc.column-reorder |
| Item types | package.type-name |
biobinoc.fasta-alignment, binoc.tabular |
| Actions | Standard actions unnamespaced; custom namespaced | add, remove, modify (standard); biobinoc.gap-shift (custom) |
The binoc.* namespace is reserved for the standard library.
Versioning¶
- Python plugins. Depend on
binocas a host package with a lower bound on the Python APIs you use. Do not add an upper bound unless you know of a specific incompatibility. - Rust (native) plugins. The Rust compatibility boundary is
binoc-sdk, notbinoc. Depend on thebinoc-sdkminor line you built against inCargo.toml; inpyproject.toml, depend onbinocwith a floor for the loader features you need. Native plugin compatibility is checked at runtime via the plugin'ssdk_version.
See Release surface and automated publishing ADR for which packages are published and why.
Where to go next¶
- Install and use plugins — from the user's side.
- Publish a plugin — packaging, entry points, and PyPI.
- Write a Python comparator / Write a Rust comparator — worked examples that produce the artifacts registered above.