PythiloLesson 14 of 15 · progress kept in this browser

Plugin registries

Some parts of an application are open-ended: export formats, payment methods, file importers. Instead of an if chain that grows with each one, keep a registry that maps a name to an implementation, and let each plugin register itself with a decorator.

class Registry:
    def __init__(self):
        self.items = {}

    def register(self, name):
        def deco(fn):
            self.items[name] = fn
            return fn
        return deco

exporters = Registry()

@exporters.register("csv")
def to_csv(rows):
    lines = [",".join(r) for r in rows]
    return "\n".join(lines)

print(exporters.items["csv"]([["a", "b"]]))

Code that uses the registry asks for a name and never imports the plugins, so adding a format means adding a function, not editing a dispatcher.

Make the registry an object rather than a module-level dict, so each test can build its own. Installed packages use the same idea at a larger scale, through entry points.

Your task

Write Registry with register(name), a decorator that returns the function unchanged and raises ValueError if the name is taken, get(name) raising a LookupError whose message lists the available names, and names() returning them sorted. Register "csv" and "json" exporters on a module-level exporters registry and write export(fmt, rows) using it.