mrkeyoor.com_
Sat 08 Aug 21:00 UTC
PyPIUtilsupdated 08 Aug 2026

catalogue

catalogue is a tiny Python registry for mapping stable string names to functions or other objects. A library creates a namespaced Registry, extensions register implementations with a decorator or direct call, and configuration can store only the chosen name instead of trying to serialize executable code. Optional Python entry-point discovery lets separately installed packages contribute implementations without an application importing each plugin module itself.

Verdict

Catalogue is a good fit for library authors who need a string-to-callable registry and standard entry-point discovery, nothing more. Application code with a handful of callbacks should use a dict, while complex plugin contracts deserve pluggy or stevedore.

API stability5/5Version 2.0.10 exposes a very small surface: create, Registry registration and lookup methods, entry-point access, find, check_exists, and RegistryError. The README says the only major compatibility boundary is that 2.x requires Python 3.6 or later, with 1.x retained for Python 2. The latest stable release dates to 2023, and the source remains compact enough that callers are unlikely to encounter accidental behavioral churn.
Docs4/5The repository README explains the serialization motivation, walks through library and user code, discusses import side effects and entry points, and documents every public method with signatures and examples. It does not have a separate versioned documentation site, a typed plugin protocol guide, or much operational discussion of global state, overwrite behavior, entry-point caching, and test isolation, all of which must be inferred from the short source.
Maintenance3/5The repository was pushed on 2026-03-27 and has only 6 open issues and pull requests, so it is not abandoned. However, PyPI 2.0.10 and the corresponding GitHub release were published on 2023-09-25. That can be reasonable for a tiny stable utility, but it also means fixes and compatibility metadata reach users slowly, and the published classifiers still emphasize Python versions as old as 3.6.
Ecosystem4/5The package is part of Explosion's Python stack and the README uses spaCy as its entry-point example. It records 5,643,155 weekly downloads and supports both PyPI and conda-forge installation, giving it far more real use than its 183 GitHub stars suggest. The ecosystem is intentionally narrow, though: it interoperates through standard Python entry points and does not offer pluggy-style hook specifications or a broader extension toolkit.

Use it if

  • You maintain a Python library that needs a small public plugin surface keyed by serializable names
  • Configuration, logs, or saved models must record which callable was selected without pickling the callable
  • Third-party distributions should expose implementations through standard Python package entry points
  • You want the same simple registry style used in the Explosion ecosystem without adopting a full plugin manager
Skip it if

Setup reality

pip install catalogue is enough on current Python, with no required configuration file and no runtime dependency for Python 3.8 or later. The published metadata still supports Python 3.6 and 3.7 by conditionally installing zipp and typing-extensions, while the README directs Python 2 users to the old 1.x line. The real setup cost is deciding namespace ownership and import behavior. catalogue.create('your_package', 'loaders') raises RegistryError if that namespace was already created in the process, so define it once in a stable module and import that object everywhere else. Decorator registration only runs when the defining module is imported. For independently distributed plugins, create the registry with entry_points=True and publish an entry-point group formed by joining the namespace with underscores, such as your_package_loaders. Entry-point objects are loaded when looked up and can raise their own import errors. Catalogue snapshots installed entry points at its own import time, so adding a distribution to a live environment does not update an already running process. Direct registration accepts any object and duplicate names overwrite silently; the package performs no signature, protocol, or return-value validation. Tests that register global entries can contaminate later tests in the same interpreter, and the only removal helper is private, so use unique test namespaces, process isolation, or carefully restore catalogue.REGISTRY in fixtures. There are no credentials, native builds, or services, but there is also no lifecycle management around plugins.

Patterns

Create one namespaced registrycreate-registry

import catalogue

loaders = catalogue.create('acme_data', 'loaders')

Create a namespace once in a stable module. A second create call for the same namespace raises RegistryError even if no entries have been registered yet.

Register a callable with a decoratorregister-decorator

@loaders.register('json')
def load_json(path):
    import json
    with open(path, encoding='utf8') as file_:
        return json.load(file_)

Registration occurs when Python imports this module. If nothing imports it, the decorator never runs and the name will be absent.

Register an existing callable directlyregister-directly

def load_text(path):
    return path.read_text(encoding='utf8')

loaders.register('text', func=load_text)

The func argument is keyword-only. Registering the same name again silently replaces the previous object in the global registry.

Use the Registry object as the decoratoruse-registry-call

@loaders('bytes')
def load_bytes(path):
    return path.read_bytes()

Registry.__call__ delegates to register. This is shorter but can be less obvious to readers unfamiliar with catalogue.

Resolve a configured implementationresolve-and-call

def load(path, loader_name: str):
    loader = loaders.get(loader_name)
    return loader(path)

An unknown name raises catalogue.RegistryError and the message lists available names. The return type is Any, so your code owns signature validation.

List direct and entry-point registrationslist-registrations

for name, loader in loaders.get_all().items():
    print(name, loader)

With entry_points=True this loads every matching entry point. Import failures inside any plugin can therefore make get_all fail.

Check for a name before lookupcheck-registration

if 'json' in loaders:
    value = loaders.get('json')(path)
else:
    value = fallback(path)

For entry-point registries, containment may load the matching plugin rather than performing a side-effect-free metadata check.

Accept plugins from installed distributionsenable-entry-points

import catalogue

renderers = catalogue.create(
    'acme_reports',
    'renderers',
    entry_points=True,
)

The entry-point group becomes acme_reports_renderers. Available entry points are captured when catalogue itself is imported.

Advertise a plugin in pyproject.tomlpublish-entry-point

[project.entry-points.acme_reports_renderers]
pdf = 'acme_pdf:render_pdf'

The group must exactly match the catalogue namespace joined by underscores. The target module and object are imported when catalogue loads that entry point.

Load one optional plugin with a fallbackload-one-entry-point

renderer = renderers.get_entry_point('pdf', default=None)
if renderer is None:
    renderer = render_text

get_entry_point returns the default only when the name is absent. An installed plugin that fails during import still raises its import exception.

Find where a registered callable came frominspect-registration

info = loaders.find('json')
print(info['module'])
print(info['file'], info['line_no'])
print(info['docstring'])

Cython and other non-inspectable callables can produce None for file and line number. find first performs a normal lookup and can load an entry point.

Give each test registry a unique namespaceavoid-test-collisions

from uuid import uuid4
import catalogue

registry = catalogue.create('tests', uuid4().hex)
registry.register('fake', func=lambda value: value)

Catalogue keeps process-wide global state and exposes no public unregister method. Unique namespaces avoid duplicate-create errors in a shared test interpreter.

Alternatives

PackageRegistryPick it when
pluggyPyPIUse it for specified hooks, multiple implementations, wrappers, ordering, and validation across a real plugin system
stevedorePyPIUse it when entry-point discovery, loading strategies, drivers, and extension managers are the main job
dependency-injectorPyPIUse it when named implementations are part of a larger dependency graph with scopes, providers, and configuration