Skip to content

Plugin Development — Your First VocabularyProviderPlugin

FCC's extensibility surface is eleven abstract base classes defined in src/fcc/plugins/base.py. The newest, VocabularyProviderPlugin (v1.2.1), is also the most common entry point for new integrators because it is the bridge the sister projects use — Athenium, Mnemosyne, AOME, CTO, and the ten constellation verticals all expose their dataclass shapes to FCC's VocabularyMappingLoader through this plugin type. This page walks you through writing one from scratch in roughly thirty minutes.

What the plugin does

FCC ships 175 packaged YAML mapping files under src/fcc/data/objectmodel/. Each file describes the FCC target side of a cross-vocabulary relationship: it names the source namespace, the source identifier, the target FCC concept, and the provenance. The loader needs to verify, at runtime, that every source_id has a real Python class behind it. A VocabularyProviderPlugin is that proof — it returns a {source_id: python_class} map and declares the namespace it owns.

The pattern is deliberately one-directional. FCC owns the mapping files; your sister project owns the source dataclass shapes; the plugin is the read-only bridge that lets FCC verify the contract without importing your project unconditionally.

The ABC you implement

The abstract base class is short. You implement three methods: plugin_meta(), get_namespace(), and get_class_map(). All three live in src/fcc/plugins/base.py starting at line 226.

# src/myproject/fcc_plugin.py
from __future__ import annotations

from fcc.plugins.base import (
    PluginMeta,
    PluginType,
    VocabularyProviderPlugin,
)


class MyProjectVocabularyProvider(VocabularyProviderPlugin):
    """Bridge for the myproject namespace."""

    def plugin_meta(self) -> PluginMeta:
        return PluginMeta(
            id="myproject-vocabulary",
            name="MyProject Vocabulary Provider",
            version="0.1.0",
            plugin_type=PluginType.VOCABULARY_PROVIDERS,
            description="Exposes MyProject dataclasses to FCC.",
            author="MyProject Team",
            source_package="myproject",
        )

    def get_namespace(self) -> str:
        return "myproject"

    def get_class_map(self) -> dict[str, type]:
        try:
            from myproject.models import Asset, Event, Measurement
        except ImportError:
            return {}
        return {
            "myproject:Asset": Asset,
            "myproject:Event": Event,
            "myproject:Measurement": Measurement,
        }

Three observations:

  1. Graceful degradation. If myproject is not installed, get_class_map returns an empty dict. The loader treats that as "no coverage for this namespace" rather than a crash.
  2. No sys.path hacks. The plugin imports its home package normally; FCC does not reach inside your code.
  3. Frozen dataclass for metadata. PluginMeta is a frozen dataclass, not Pydantic, matching FCC's convention.

Declare the entry point

FCC discovers plugins through setuptools entry points. Add the following to your project's pyproject.toml:

[project.entry-points."fcc.plugins.vocabulary_providers"]
myproject = "myproject.fcc_plugin:MyProjectVocabularyProvider"

The group name is fcc.plugins.vocabulary_providers; the key (myproject) must be unique across all installed providers; the value points at your ABC implementation.

Ship the companion YAML

In FCC (not in your project — FCC owns this file) you add a mapping file at src/fcc/data/objectmodel/myproject_vocabulary_mappings.yaml with entries like:

- source_vocabulary: myproject
  source_id: myproject:Asset
  target_concept: fcc:CoreAsset
  notes: "Physical or digital asset tracked by MyProject."
  provenance: "MyProject v0.9 domain model"

The source_id keys must exactly match the keys in your get_class_map() return value. The VocabularyMappingLoader walks both sides and reports mismatches as either missing_source_ids (YAML entry with no Python class) or unmapped_classes (Python class the YAML never names).

Verify the plugin is discovered

With both sides in place:

pip install -e .        # in your project
pip install -e .        # in FCC (to pick up the new YAML)

python -c "
from fcc.admin.readers import PluginReader
pr = PluginReader()
print(pr.load().get('fcc.plugins.vocabulary_providers', []))
print(pr.health())
"

Your plugin key should appear and its health should be "ok".

Best practices

  • Version your plugin alongside your project. A plugin bump is MINOR; a breaking contract change is MAJOR.
  • Keep the class map stable. Removing a key in a patch release is a semver violation. Add and deprecate; do not delete.
  • Do not emit events from this plugin. Vocabulary providers are read-only. If you need to react to runtime events, use a separate EventSubscriberPlugin.
  • Test with FCC installed but your project NOT installed. The graceful-degradation path is the most commonly missed code path.