python ini 105 lines · 4 tabs

Plugin Registry with Class Decorators and setuptools Entry-Point Discovery

Shared by codesnips Jul 2026
4 tabs
import abc
from typing import IO


class Exporter(abc.ABC):
    name: str = ""

    @abc.abstractmethod
    def export(self, rows: list[dict], out: IO[str]) -> None:
        """Write rows to the given text stream."""
        raise NotImplementedError
4 files · python, ini Explain with highlit

This snippet shows a small but complete plugin system for a data-export tool, built from a decorator-based in-process registry plus lazy discovery of third-party plugins through packaging entry points. The pattern solves a recurring extensibility problem: a host application needs to accept new implementations of some interface (exporters, codecs, transports) without hard-coding imports of every implementation, and ideally without knowing about plugins shipped in separate packages at all.

In registry.py, ExporterRegistry holds a dict mapping a string key to an Exporter subclass. The register classmethod returns a decorator so a class can be added at definition time with @ExporterRegistry.register("csv"). The decorator validates that the decorated class is actually a subclass of Exporter and rejects duplicate names, which turns silent overwrites into a loud RegistryError at import time. Registering the class object rather than an instance keeps construction lazy: the host decides when and how to instantiate, passing whatever constructor arguments a given call needs.

The interesting part is discover_entry_points, which pulls in plugins from other installed distributions. It uses importlib.metadata.entry_points to look up every entry point advertised under the myapp.exporters group. Each EntryPoint is only imported when ep.load() is called, so packages that expose plugins pay no import cost until discovery runs. A guard (_discovered) makes the method idempotent, and a try/except around ep.load() means one broken plugin package logs a warning instead of taking down the whole application. This is the same mechanism pytest, flake8, and many CLIs use for their plugin ecosystems.

The Exporter base class in base.py defines the contract: a name attribute and an export method that subclasses must implement. Making it an abc.ABC with an @abstractmethod means a half-finished plugin fails fast at instantiation rather than at the point of use.

plugins.py demonstrates first-party plugins. JsonExporter and CsvExporter register themselves simply by being imported, thanks to the decorator running at class-definition time. This is why the host must import the module for local plugins to appear, while external plugins arrive purely through entry points.

Finally, pyproject.toml shows the packaging side. The [project.entry-points."myapp.exporters"] table is where a downstream package would advertise parquet = mypkg.exporters:ParquetExporter; the string on the right is exactly what ep.load() imports and returns. The trade-offs are worth noting: entry-point discovery adds a small startup cost and depends on correct package metadata, and the loose string keys mean typos surface at runtime. In exchange the host stays completely decoupled from concrete implementations, and third parties extend it without editing its source. A developer reaches for this when building anything meant to be extended by plugins across package boundaries.


Related snips

python
import os
import stat

for root, _dirs, files in os.walk('/etc'):
    for name in files:
        path = os.path.join(root, name)

Python security audit script for exposed risky filesystem state

python auditing host-security
by Kai Nakamura 1 tab
python
class Product(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    margin = models.DecimalField(max_digits=5, decimal_places=2, blank=True)

Django model signals vs overriding save

django python models
by Priya Sharma 2 tabs
python
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [

Django URL namespacing and reverse lookups

django python urls
by Priya Sharma 3 tabs
python
import graphene
from graphene_django import DjangoObjectType
from blog.models import Post, Comment


class PostType(DjangoObjectType):

Django GraphQL with Graphene

django python graphql
by Priya Sharma 2 tabs
python
from django.db.models import Count, Avg, Sum, Q, F
from django.views.generic import TemplateView
from products.models import Product, Order, OrderItem


class DashboardView(TemplateView):

Django aggregation with annotate for statistics

django python database
by Priya Sharma 1 tab
python
from rest_framework import permissions


class IsOwner(permissions.BasePermission):
    """Allow only object owner to access."""

Django REST Framework permissions and authorization

django python rest
by Priya Sharma 2 tabs

Share this code

Here's the card — post it anywhere.

Plugin Registry with Class Decorators and setuptools Entry-Point Discovery — share card
Link copied