Registry Patterns
This guide explains different registry patterns and when to use each one.
Pattern A: Metaclass Registry (AutoRegisterMeta)
Use AutoRegisterMeta when:
1:1 mapping between classes and plugins
Automatic discovery and registration needed
Registration happens at class definition time
Simple metadata (just a key and maybe one secondary registry)
Example
from metaclass_registry import AutoRegisterMeta
class MicroscopeHandler(metaclass=AutoRegisterMeta):
__registry_key__ = 'microscope_type'
microscope_type = None
class ImageXpressHandler(MicroscopeHandler):
microscope_type = 'imagexpress'
HANDLERS = MicroscopeHandler.__registry__
Advantages
Zero boilerplate
Automatic registration
Type-safe with mypy
Registry inheritance
Disadvantages
Limited to 1:1 class-to-plugin mapping
Less flexible than manual registration
Pattern B: Service Pattern
Use Service Pattern when:
Many-to-one mapping (multiple items per plugin)
Complex metadata (8+ fields)
Need aggregation across multiple sources
Dynamic registration after class creation
Example
# Not provided by metaclass-registry
# Use manual registration with dataclasses for metadata
from dataclasses import dataclass
from typing import Dict, Callable
@dataclass
class FunctionMetadata:
name: str
category: str
library: str
function: Callable
# ... more fields
FUNCTIONS: Dict[str, FunctionMetadata] = {}
def register_function(metadata: FunctionMetadata):
FUNCTIONS[metadata.name] = metadata
Pattern C: Functional Registry
Use Functional Registry when:
Simple type-to-handler mappings
No state needed
Functional programming style preferred
Very simple use cases
Example
# Simple dict for type mappings
TYPE_HANDLERS = {
'int': int_handler,
'str': str_handler,
'float': float_handler,
}
Pattern D: Manual Registration
Use Manual Registration when:
Complex initialization logic required
Explicit control over registration timing needed
Very few plugins (< 3)
Registration happens after class creation
Example
SERVERS = {}
class ZMQServer:
def __init__(self, config):
self.config = config
# Manual registration after initialization
server = ZMQServer(config)
SERVERS['zmq'] = server
Comparison Table
Pattern |
Use Case |
Complexity |
Boilerplate |
|---|---|---|---|
A: AutoRegisterMeta |
Simple 1:1 class plugins |
Low |
None |
B: Service Pattern |
Complex metadata, many-to-one |
Medium |
Medium |
C: Functional |
Simple type mappings |
Very Low |
None |
D: Manual |
Complex initialization |
Medium |
High |
Advanced Pattern A Features
Secondary Registries
Auto-populate related registries:
from metaclass_registry import AutoRegisterMeta, SecondaryRegistry, PRIMARY_KEY
METADATA_HANDLERS = {}
class MicroscopeHandler(metaclass=AutoRegisterMeta):
__registry_key__ = 'microscope_type'
__secondary_registries__ = [
SecondaryRegistry(
registry_dict=METADATA_HANDLERS,
key_source=PRIMARY_KEY,
attr_name='metadata_handler_class'
)
]
microscope_type = None
metadata_handler_class = None
class ImageXpressHandler(MicroscopeHandler):
microscope_type = 'imagexpress'
metadata_handler_class = ImageXpressMetadata
# Primary registration
print(MicroscopeHandler.__registry__) # {'imagexpress': ImageXpressHandler}
# Secondary registration
print(METADATA_HANDLERS) # {'imagexpress': ImageXpressMetadata}
Custom Key Extractors
Derive keys from class names:
from metaclass_registry import AutoRegisterMeta
def extract_microscope_key(class_name, cls):
"""Extract 'foo' from 'FooHandler'."""
if class_name.endswith('Handler'):
return class_name[:-7].lower()
return None
class Handler(metaclass=AutoRegisterMeta):
__registry_key__ = 'handler_type'
__key_extractor__ = extract_microscope_key
handler_type = None
class ImageXpressHandler(Handler):
pass # Automatically registered as 'imagexpress'
Registry Caching
Automatically cache discovered plugins:
from metaclass_registry import AutoRegisterMeta, LazyDiscoveryDict
# Enable caching (default)
class Plugin(metaclass=AutoRegisterMeta):
__registry_key__ = 'name'
# Disable caching
registry = LazyDiscoveryDict(enable_cache=False)
# ... use in RegistryConfig
Migration Guide
From Custom Metaclass to AutoRegisterMeta
Before:
class MyPluginMeta(type):
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
if hasattr(cls, 'plugin_name') and cls.plugin_name:
PLUGINS[cls.plugin_name] = cls
return cls
PLUGINS = {}
class PluginBase(metaclass=MyPluginMeta):
plugin_name = None
After:
from metaclass_registry import AutoRegisterMeta
class PluginBase(metaclass=AutoRegisterMeta):
__registry_key__ = 'plugin_name'
plugin_name = None
PLUGINS = PluginBase.__registry__
Best Practices
Use Pattern A for most plugin systems - It handles 90% of use cases
Choose explicit keys over extractors - More maintainable
Enable caching for production - Faster startup times
Use registry inheritance - Clean hierarchies
Document your registry structure - Help users understand the system