Codumentor logo Codumentor

Plugin Dependencies

A plugin can declare ordered dependencies on other plugins. The loader resolves them topologically before falling back to the existing priority integer for tie-breaks within each layer.

See also: Plugin Distribution, Backend Hooks.


Why

Hook order is observable: an onContextReady handler that reads branch_overrides only sees a useful value if branch_switching has already populated it. Before this field, ordering was coupled to a single global priority: int shared by every plugin — collisions were silent and forced magic numbers.

Declaring dependencies: ["branch_switching"] makes the constraint explicit, self-documenting, and verified at load time.

Where to Declare

Two equivalent surfaces:

1. Distributable Plugin (plugin.yaml)

name: my_plugin
version: 1.0.0
has_backend: true
entry_module: my_plugin.plugin
entry_class: MyPlugin
priority: 50
dependencies:
  - branch_switching
  - permission_bridge

The dependencies field is part of PluginPackageManifest (src/codumentor/plugins/package.py). Default is [] — existing manifests need no change.

2. Built-in or Inline (meta)

class MyPlugin:
    meta = {
        "name": "my_plugin",
        "version": "1.0.0",
        "priority": 50,
        "dependencies": ["branch_switching"],
    }

The loader reads meta["dependencies"] for in-tree plugins. Same semantics as plugin.yaml.

3. Per-deployment Override (codumentor.yaml)

plugins:
  - module: codumentor.plugins.my_plugin
    class: MyPlugin
    dependencies: ["branch_switching"]

A dependencies entry in codumentor.yaml overrides whatever the plugin's meta (or external manifest) declared. Use this sparingly — it lets a deployment fix a load-order bug without rebuilding the plugin, but it also drifts from upstream.

Resolution Rules

  1. Topological sort first. Each plugin appears after every plugin it depends on, transitively.
  2. Priority within a layer. Plugins whose dependencies are all satisfied at the same point sort by priority integer (lower number runs firstpriority: 4 registers before priority: 6), preserving the pre-existing behaviour for plugins that don't declare dependencies.
  3. Names match meta["name"]. A dependency string "branch_switching" must match another loaded plugin's meta["name"] (or external manifest name).

Failure Modes

The two failures are treated differently, and the difference matters when you
decide whether to declare an edge at all:

SituationSeverityWhat happens
Declared dependency is not loadedSoft — startup continuesThe edge is dropped and the loader logs Plugin 'my_plugin' declares dependency 'foo', which is not among the loaded plugins; ignoring (plugin will load at default ordering for its layer). Your plugin still loads, ordered only by priority within its layer.
Dependency graph has a cycleFatal — startup abortsValueError: Plugin dependency cycle detected: a -> b -> a. Break the cycle by removing one edge (often one direction was unintended).

A missing dependency is not fatal. Declaring an edge on a plugin a given
deployment happens not to run costs you a warning line, not a failed startup —
so declaring the edge is the right call whenever the ordering genuinely matters
to you. What you do not get is a guarantee: the dependency being absent is
exactly the case where your handler runs without the state it wanted.

So an edge buys you ordering, not presence. Pair it with a runtime check for
anything you actually read — peek at ctx["scratchpad"] for the key the other
plugin populates, or ask a collect point,
and degrade gracefully when the answer is empty.

When Not to Use

Optional integrations are not on this list. If your plugin works fine without
X but must run after X when X is present, declaring the edge is correct: you get
the ordering where X exists and a warning where it doesn't.

file_links will eventually merge with the permalink rewrite pipeline so locally-modified or generated files get correct download links. That merger consumes branch_overrides from branch_switching, so the merged plugin will declare:

meta = {
    "name": "file_links",
    "dependencies": ["branch_switching"],
}

Today file_links runs with dependencies: [] simply because the consumer code
isn't wired up yet: there is nothing to order against. The edge can be added the
moment the consumer lands — deployments that run without branch_switching
(selfdev configs commonly do) will log one warning and carry on, so nothing has
to wait for universal availability.