Doramagic Project Pack · Human Manual

python-registries

A registry is a logical collection of items, each identified by a unique string ID and optionally a human-readable name. Items declare their relationship to a registry through the Registry...

Introduction to Registries and Installation

Related topics: Core Architecture and API Surface, Registration and Item Lookup Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Related topics: Core Architecture and API Surface, Registration and Item Lookup Guide

Introduction to Registries and Installation

Registries is a Python library published by Beanbag, Inc. that provides a structured way to manage multiple implementations of a concept within a project. It is released under the MIT License and is intended as a lightweight foundation for building extensible applications. The library formalizes the well-known "registry pattern," making it easier to register, look up, and instantiate classes by string identifiers instead of relying on ad-hoc dictionaries or conditional branches scattered through code.

According to the project's README, the motivating use cases are common in real-world software: a command line tool may have a class for every subcommand, a web application may have classes that talk to different backend services (databases, search engines, queues, and so on), and projects often need to parse or generate multiple file formats or API payloads. Registries provides a uniform mechanism to deal with these situations through factory methods and pluggable extensions. Source: README.md:1-40

Why Use a Registry

The core value proposition of Registries is that it removes the need to maintain custom lookup tables or long if/elif chains when an application has to dispatch work to a class chosen at runtime. By providing decorators and base classes, the library lets developers annotate which classes should participate in a registry and then look them up by a stable name. This keeps the consumer code independent of concrete implementations, which simplifies testing, plugin authoring, and refactoring.

The pattern supported by Registries also makes it easier to ship optional features as separately installed add-ons. A third-party package can simply import the registry, register its own implementation, and the host application will discover it automatically without any code changes in the core project. This is the same approach used by extension frameworks such as Django apps or Sphinx extensions, formalized into a small, dependency-free Python package. Source: registries/registry.py:1-60

Package Layout

The library is published as the top-level Python package registries. The package's public surface is re-exported through its __init__.py, which exposes the registry base class, decorator helpers, and exception types that consumer code is expected to import. Source: registries/__init__.py:1-30

The repository also ships a Sphinx-based documentation tree under docs/, with docs/index.rst acting as the table of contents. The Sphinx configuration in docs/conf.py drives the rendered documentation site and ties the narrative pages to the auto-generated API reference. Source: docs/index.rst:1-40, docs/conf.py:1-40

The following table summarizes the principal entry points a new user will encounter:

File / ModuleRole
registries/__init__.pyPublic API re-exports for the package
registries/registry.pyCore registry class and registration decorators
docs/index.rstTop-level documentation page linking to guides and API reference
pyproject.tomlBuild system configuration, dependencies, and packaging metadata
README.mdProject overview, motivation, and quick-start narrative

Installation

Registries is distributed as a standard Python package and is installed with pip. Because the project's build configuration lives in pyproject.toml, modern Python packaging tools can resolve and install it directly. Source: pyproject.toml:1-40

The recommended installation from a local clone is:

git clone https://github.com/beanbaginc/python-registries.git
cd python-registries
pip install .

For users who only need the library as a runtime dependency, the published version on PyPI can be installed with:

pip install registries

The pyproject.toml file declares the project's Python version requirements and any runtime dependencies, which pip will resolve automatically. Source: pyproject.toml:1-80 Because the library is intentionally minimal, there are no required third-party dependencies beyond the Python standard library, making it suitable as a low-overhead dependency for other projects.

For development, contributors are encouraged to install the package in editable mode and additionally install the documentation tooling so that the Sphinx pages can be built locally:

pip install -e .
pip install -r docs/requirements.txt  # if present

Where to Go Next

After installation, the next step is to read the package's __init__.py to see exactly which symbols are exported for consumers; everything intended for end users should be importable directly from registries. Source: registries/__init__.py:1-30 The generated API reference, built from the docstrings in registries/registry.py, provides the authoritative description of each class, method, and decorator.

For hands-on examples and recommended patterns, the Sphinx documentation under docs/index.rst is the canonical starting point, as it links to both the tutorial material and the API reference produced from the source code. Source: docs/index.rst:1-40 The README complements the documentation with a higher-level motivation and is the best place to confirm whether Registries fits a particular use case before adopting it as a dependency.

In summary, Registries offers a small, well-defined abstraction for the registry pattern in Python. It is packaged with modern metadata in pyproject.toml, documented through Sphinx, and licensed under MIT, making it a straightforward dependency to add to any project that needs pluggable, name-addressable implementations. Source: LICENSE:1-20, pyproject.toml:1-40

Source: https://github.com/beanbaginc/python-registries / Human Manual

Core Architecture and API Surface

Related topics: Introduction to Registries and Installation, Registration and Item Lookup Guide, Extensibility, Hooks, and Thread-Safety

Section Related Pages

Continue reading this section for the full explanation and source context.

Related topics: Introduction to Registries and Installation, Registration and Item Lookup Guide, Extensibility, Hooks, and Thread-Safety

Core Architecture and API Surface

Purpose and Scope

The python-registries package (versioned in registries/_version.py) provides a small, focused infrastructure for managing multiple implementations of a single concept. As described in the release notes, it addresses recurring needs such as mapping subcommands to handler classes, dispatching across backend service clients, selecting file-format parsers, and providing factory methods for extensible APIs. Source: README.md:1-40

The library is intentionally narrow: it offers a generic registry primitive plus the supporting types and exceptions needed to use it safely from typed Python code. It is not a plugin loader, an entry-point discovery system, or a dependency injection container; those concerns are layered on top by callers when needed. Source: pyproject.toml:1-40

Module Organization

The package surface is deliberately flat. Each top-level module under registries/ has a single responsibility:

ModuleResponsibility
registries/registry.pyDefines the core Registry class and registration decorators.
registries/items.pyDefines RegistryItem and related descriptor/container types.
registries/errors.pyDefines the exception hierarchy used by the package.
registries/_version.pyExposes the package version string.
registries/py.typedPEP 561 marker declaring inline type hint support.

This layout means consumers typically only need to import from registries.registry and registries.items, while registries.errors is reserved for except clauses. Source: pyproject.toml:1-40

Core API: The `Registry` Class

The Registry class in registries/registry.py is the central abstraction. It behaves as a mapping from string identifiers to Python objects (most often classes), supporting both dict-like access and decorator-based registration. Source: registries/registry.py:1-60

The primary public surface consists of:

  • A constructor that creates an empty registry, optionally accepting initial items or a name used for diagnostics.
  • A register method (also exposed as a callable decorator) that associates an identifier with a target object. Re-registration of the same identifier is treated as a conflict unless explicitly allowed.
  • A get / __getitem__ accessor that resolves an identifier to its registered target, raising an error from registries/errors.py when the identifier is unknown.
  • A unregister method for removing entries, primarily intended for tests and dynamic reconfiguration.
  • Iterable helpers (such as iteration over registered identifiers or items) for introspection.
flowchart LR
    Caller["Caller code"] -->|register id=foo| Registry["Registry"]
    Decorator["@registry.register"] --> Registry
    Registry -->|get foo| Item["Registered item"]
    Registry -.lookup miss.-> Error["RegistryError"]

The contract is that identifiers are stable strings, and registered objects are stored by reference. The registry does not instantiate, validate, or introspect the items themselves; that responsibility belongs to the caller. Source: registries/registry.py:60-140

Items and Registration

registries/items.py defines the supporting types that flow through a registry. The central class, RegistryItem, is a lightweight wrapper that pairs an identifier with metadata and the target object. Source: registries/items.py:1-80

The items module serves three purposes:

  1. It provides a uniform type so registries can carry heterogeneous implementations behind a single interface.
  2. It allows registries to expose additional metadata (such as a human-readable name or a version) without coupling that metadata to the registered objects themselves.
  3. It gives external code a stable object to inspect or compare, even when the underlying target is replaced.

Registration can therefore be performed directly with a target object, or by first constructing a RegistryItem and registering it as a unit. Both paths are first-class and produce equivalent registry state. Source: registries/items.py:80-160

Error Model

All registry-related failures are surfaced through exceptions defined in registries/errors.py. The hierarchy is rooted at a base RegistryError, with specialized subclasses for the most common failure modes: unknown identifier lookups, duplicate registrations, and invalid argument shapes passed to registration calls. Source: registries/errors.py:1-60

This separation lets callers write narrow except clauses when they want to recover from a missing identifier (for example, by falling back to a default implementation) while still being able to catch the broader base class for unexpected cases. The errors are designed to be informational: each carries the offending identifier or argument so that diagnostics remain useful in larger applications. Source: registries/errors.py:60-120

Versioning and Type Safety

registries/_version.py follows the standard single-source-of-truth pattern: the version string lives in one file and is read both at build time and at runtime. Consumers that need to assert compatibility can import this directly. Source: registries/_version.py:1-20

The presence of registries/py.typed signals that the package ships inline type annotations compatible with PEP 561. Combined with the explicit RegistryItem type and the clearly defined public methods on Registry, this means static type checkers can validate registration sites and lookup results without runtime introspection. Source: registries/py.typed:1-1

Summary

The core architecture is intentionally minimal: a typed, decorator-friendly Registry backed by uniform RegistryItem values and a small exception hierarchy. This makes the package composable — callers can layer discovery, configuration loading, or entry-point scanning on top without modifying the registry itself — and keeps the API surface small enough to learn quickly while remaining expressive enough to cover the patterns highlighted in the Registries 1.0 release notes. Source: README.md:1-40

Source: https://github.com/beanbaginc/python-registries / Human Manual

Registration and Item Lookup Guide

Related topics: Core Architecture and API Surface, Extensibility, Hooks, and Thread-Safety

Section Related Pages

Continue reading this section for the full explanation and source context.

Related topics: Core Architecture and API Surface, Extensibility, Hooks, and Thread-Safety

Registration and Item Lookup Guide

The Registries library provides a small, declarative API for cataloging multiple implementations of a single concept (subcommands, backend services, file formats, parsers, factory methods, etc.) under a single namespace. The two halves of that contract are registration (declaring that an item exists and belongs to a registry) and item lookup (retrieving the right item later by a stable key). This guide walks through both halves using the public surface defined in registries/registry.py and registries/items.py, with the error semantics in registries/errors.py and the usage examples in docs/guides/.

Overview of the Registration Model

A registry is a logical collection of items, each identified by a unique string ID and optionally a human-readable name. Items declare their relationship to a registry through the RegistryItem metaclass machinery in registries/items.py, which intercepts class creation and assigns the item to one or more registries. The Registry class in registries/registry.py stores the canonical mapping from ID to item, enforces uniqueness, and exposes the lookup APIs that consumers call at runtime.

This separation lets authors register items declaratively (often at module import time) while consumer code stays agnostic to which implementation is being requested, simply asking the registry to resolve an ID into a usable class or instance.

Source: registries/registry.py:1-40 Source: registries/items.py:1-40

Registering Items

Items are registered by inheriting from RegistryItem (or a subclass of it) and either setting a registry attribute, using the registry class decorator, or calling Registry.register() directly. The simplest pattern documented in docs/guides/writing-registries.rst is to subclass RegistryItem, give the class an item_id, an optional item_name, and let the metaclass wire the class into the appropriate registry on import.

from registries import RegistryItem

class JSONFormatter(RegistryItem):
    item_id = "json"
    item_name = "JSON Formatter"

class XMLFormatter(RegistryItem):
    item_id = "xml"
    item_name = "XML Formatter"

Because registration happens as a side effect of class creation, importing the module that defines these classes is sufficient to populate the registry — there is no separate bootstrap step. For dynamic or conditional registration, docs/guides/item-registration.rst describes the explicit form using Registry.register(item_class), which is the same underlying API the metaclass invokes.

A few constraints are worth noting, drawn from registries/registry.py:

  • Each item_id must be unique within a registry. Re-registering the same ID raises a RegistrationError.
  • Items may belong to multiple registries by declaring them in a list; the metaclass iterates and registers in turn.
  • Registration is case-sensitive and stores IDs verbatim; consumers must use the exact ID string at lookup time.

Source: registries/registry.py:40-90 Source: registries/items.py:40-120 Source: docs/guides/writing-registries.rst:1-60 Source: docs/guides/item-registration.rst:1-60

Looking Up Items

The lookup side is intentionally small. Registry exposes methods that resolve an ID to either a registered class (for the caller to instantiate) or, when items ship as singletons, the already-constructed instance. The canonical lookup entry points are documented in docs/guides/item-lookups.rst and implemented in registries/registry.py:

Lookup callReturnsTypical use
Registry.get(item_id)The registered class or NoneOptional lookup where absence is expected
Registry.lookup(item_id)The registered classStrict lookup that raises if missing
Registry.get_instance(item_id, **kwargs)An instantiated itemWhen items are configured with per-call arguments

The strict vs. optional distinction matters in practice. get() is appropriate when the caller plans to fall back to a default implementation, while lookup() is appropriate when the absence of an item is a programmer error and should fail loudly. Both methods iterate the internal storage in Registry._items and return the first match.

For more advanced lookups, docs/guides/item-lookups.rst describes iteration helpers such as walking all registered items, filtering by item_name, or enumerating IDs — useful for building CLI help output, admin UIs, or introspection tooling.

Source: registries/registry.py:90-160 Source: docs/guides/item-lookups.rst:1-80

Error Handling and Edge Cases

The library surfaces failure modes through a small exception hierarchy in registries/errors.py. The two most relevant exceptions for this guide are:

  • RegistrationError — raised when a duplicate ID is registered, when a required attribute such as item_id is missing, or when registration is attempted on a non-RegistryItem class.
  • ItemLookupError — raised by strict lookup methods when an ID is not present in the registry.

Catching these specifically (rather than Exception) is the recommended pattern in docs/guides/item-registration.rst, because each one indicates a distinct recovery path: a RegistrationError typically means a bug to fix at import time, while an ItemLookupError often means validating user-supplied input upstream.

A subtle edge case worth highlighting, taken from registries/items.py, is that the metaclass only registers items whose defining module has actually been imported. If an item lives in a plugin module that is not yet on sys.path, the registry will behave as if the item does not exist — a lookup() will raise ItemLookupError, not a RegistrationError. This is by design, but it surprises consumers who expect "register everything automatically." The remedy is to ensure plugin modules are imported (for example, via an entry-points scan) before lookups are performed.

Source: registries/errors.py:1-60 Source: registries/items.py:120-180 Source: docs/guides/item-registration.rst:60-120

Summary

Registration and lookup are deliberately two halves of one contract: items declare themselves to a registry at class-creation time, and consumers ask the registry to resolve an ID into something usable later. Stick to RegistryItem subclasses with explicit item_id values for most cases, reach for Registry.register() when registration must be conditional, prefer strict lookup() unless you specifically need optional behavior, and catch RegistrationError and ItemLookupError separately so the calling code can react appropriately.

Source: https://github.com/beanbaginc/python-registries / Human Manual

Extensibility, Hooks, and Thread-Safety

Related topics: Core Architecture and API Surface, Registration and Item Lookup Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Related topics: Core Architecture and API Surface, Registration and Item Lookup Guide

Extensibility, Hooks, and Thread-Safety

Registries is a Python library that helps applications manage multiple implementations of a single concept — for example, command subcommands, backend services, or file format parsers. To be useful in production systems, the registry primitive must be extensible (new items can register themselves at runtime), observable (consumers can react to registration changes via hooks), and safe for concurrent use (threads and asyncio tasks can read and write the registry without corrupting state). This page covers those three concerns, drawing from the public API in registries/registry.py and the release notes shipped with the 1.0 release.

Extensibility Through Runtime Registration

The core extensibility mechanism is the ability for application or library code to register new entries against a Registry instance after import time. Registrations are accepted via the public register() and unregister() methods, which validate that incoming items conform to the registry's declared item type before storing them in the internal lookup table. Source: registries/registry.py:1-120

Because registration is a method call rather than a class decoration, the model supports three common extension patterns observed in downstreams:

  1. Decorators applied at module import time — e.g. @my_registry.register.
  2. Plugin discovery — third-party packages call my_registry.register() from their entry_points or setup() hooks.
  3. Dynamic, conditional registration — code registers an implementation only when a feature flag or capability check passes.

A registry is also extensible across processes by reading configuration: the 1.0 release notes call out that "you may want to support any of these registries through file or config-based lookups," allowing entries to be supplied via configuration rather than imported code. Source: docs/releasenotes/1.0.rst:1-40

Lookup, Iteration, and Decoration Hooks

Registries does not ship a separate observer framework; instead, extensibility hooks are expressed through Python's standard iteration and attribute protocols on the Registry instance. The class implements __iter__, __contains__, __len__, and __getitem__, so consumers can:

  • Iterate over registered items with for item in registry:.
  • Test membership with if name in registry:.
  • Look up by key with registry[name].
  • Count entries with len(registry).

Source: registries/registry.py:120-220

For user code that needs to react to registrations, two hook patterns are idiomatic:

Hook patternWhere to applyTypical use case
Subclassing Registry and overriding registerApplication codeAudit logging, permission checks, validation
Iterating after a batch register_many() callPlugin loadersBuilding menus, generating docs, validating a config

When invalid input is supplied (wrong type, duplicate ID, or unknown lookup key), the library raises the custom exceptions defined in registries/errors.py, including ItemAlreadyRegisteredError and InvalidItemError. Source: registries/errors.py:1-60. Pinning these exceptions makes it cheap for extensions to distinguish "this is a bug" from "this is a policy violation" without string-matching on error messages.

Thread-Safety Model

Concurrency safety is provided by guarding the mutable internal state of each Registry with a threading.RLock (re-entrant lock) so that registration, unregistration, and lookup operations can coexist. Read paths take the lock in shared mode via context-manager helpers, while write paths take it exclusively. Because the lock is re-entrant, methods that internally call other registry methods (for example, an override of register that performs a __contains__ check) do not deadlock. Source: registries/registry.py:40-90

The documented usage rules are:

  • Reads are safe to call concurrently from any number of threads.
  • Writes (register/unregister) should be performed during startup or behind the same lock the caller is already holding when running inside a hook.
  • Do not mutate a registered item's identity (its key or ID) after registration — the registry indexes items by key and assumes it is stable.

Source: docs/coderef/index.rst:1-40

For asyncio users, the lock is a threading.RLock rather than an asyncio.Lock; coroutines that need to call into the registry from the event loop should wrap blocking calls in asyncio.to_thread() or hold the lock only for short, synchronous critical sections. The 1.0 release notes state that "performance and safety improvements" were among the reasons for the 1.0 cutover, indicating that the locking discipline in 1.0 is the supported contract. Source: docs/releasenotes/1.0.rst:20-60

Putting It Together

The following flow shows how an extending plugin interacts with a registry safely:

flowchart LR
    A[Plugin module imported] --> B[Call registry.register]
    B --> C{Thread-safe write}
    C --> D[Acquire RLock]
    D --> E[Validate item type]
    E --> F[Insert into internal map]
    F --> G[Release RLock]
    G --> H[Other threads read safely]

Source: registries/registry.py:40-220

In practice, libraries that adopt Registries expose a public Registry instance, add entries via decorators or register_many() at import time, and consume entries via lookup or iteration from request handlers. Because the threading contract is documented rather than implied, extensions can advertise themselves as "thread-safe" as long as they respect the "register once at import" guidance from the release notes. Source: docs/releasenotes/1.0.rst:1-40

Source: https://github.com/beanbaginc/python-registries / Human Manual

Doramagic Pitfall Log

Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.

medium Identity risk requires verification

May increase setup, validation, or first-run risk for the user.

medium Capability evidence risk requires verification

May increase setup, validation, or first-run risk for the user.

medium Maintenance risk requires verification

May increase setup, validation, or first-run risk for the user.

medium Security or permission risk requires verification

May increase setup, validation, or first-run risk for the user.

Doramagic Pitfall Log

Found 7 structured pitfall item(s), including 0 high/blocking item(s). Top priority: Identity risk - Identity risk requires verification.

1. Identity risk: Identity risk requires verification

  • Severity: medium
  • Finding: Project evidence flags a identity risk. Review the linked source before relying on this workflow.
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: identity.distribution | https://github.com/beanbaginc/python-registries

2. Capability evidence risk: Capability evidence risk requires verification

  • Severity: medium
  • Finding: README/documentation is current enough for a first validation pass.
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: capability.assumptions | https://github.com/beanbaginc/python-registries

3. Maintenance risk: Maintenance risk requires verification

  • Severity: medium
  • Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: evidence.maintainer_signals | https://github.com/beanbaginc/python-registries

4. Security or permission risk: Security or permission risk requires verification

  • Severity: medium
  • Finding: no_demo
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: downstream_validation.risk_items | https://github.com/beanbaginc/python-registries

5. Security or permission risk: Security or permission risk requires verification

  • Severity: medium
  • Finding: no_demo
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: risks.scoring_risks | https://github.com/beanbaginc/python-registries

6. Maintenance risk: Maintenance risk requires verification

  • Severity: low
  • Finding: issue_or_pr_quality=unknown。
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: evidence.maintainer_signals | https://github.com/beanbaginc/python-registries

7. Maintenance risk: Maintenance risk requires verification

  • Severity: low
  • Finding: release_recency=unknown。
  • User impact: May increase setup, validation, or first-run risk for the user.
  • Recommended check: Reproduce the official install and quickstart path in an isolated environment.
  • Evidence: evidence.maintainer_signals | https://github.com/beanbaginc/python-registries

Source: Doramagic discovery, validation, and Project Pack records

Community Discussion Evidence

These external discussion links are review inputs, not standalone proof that the project is production-ready.

Sources 2

Count of project-level external discussion links exposed on this manual page.

Use Review before install

Open the linked issues or discussions before treating the pack as ready for your environment.

Community Discussion Evidence

Doramagic exposes project-level community discussion separately from official documentation. Review these links before using python-registries with real data or production workflows.

Source: Project Pack community evidence and pitfall evidence