Registry System¶
This page describes the
coola.registry package, which provides thread-safe registry
implementations for storing and managing typed mappings.
Prerequisites: You'll need to know a bit of Python. For a refresher, see the Python tutorial.
Overview¶
The coola.registry package provides two types of registries for managing key-value mappings:
Registry: A generic key-value registry for any hashable keysTypeRegistry: A specialized type-based registry with Method Resolution Order (MRO) support
Both registries are thread-safe and support standard dictionary operations through operator overloading.
Registry (Generic Key-Value)¶
Basic Usage¶
The Registry class provides a thread-safe container for storing and retrieving values by key:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]()
>>> registry.register("key1", 42)
>>> registry.get("key1")
42
Dictionary-Style Operations¶
The registry supports standard dictionary operations:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]()
>>> registry["key1"] = 100
>>> "key1" in registry
True
>>> registry["key1"]
100
>>> del registry["key1"]
>>> "key1" in registry
False
Initialization with Data¶
You can initialize a registry with existing data:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]({"a": 1, "b": 2})
>>> len(registry)
2
>>> registry.get("a")
1
Safe Registration¶
By default, registering a duplicate key raises an error to prevent accidental overwriting:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]()
>>> registry.register("key1", 42)
>>> registry.register("key1", 100) # doctest: +SKIP
Traceback (most recent call last):
...
RuntimeError: A value is already registered for 'key1'...
To allow overwriting, use exist_ok=True:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]()
>>> registry.register("key1", 42)
>>> registry.register("key1", 100, exist_ok=True)
>>> registry.get("key1")
100
Bulk Registration¶
Register multiple key-value pairs at once:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]()
>>> registry.register_many({"key1": 42, "key2": 100, "key3": 7})
>>> len(registry)
3
>>> registry.get("key2")
100
Bulk updates with exist_ok:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]({"key1": 1})
>>> registry.register_many({"key1": 10, "key4": 4}, exist_ok=True)
>>> registry.get("key1")
10
>>> registry.get("key4")
4
Unregistering Keys¶
Remove a key and get its value:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]({"key1": 42, "key2": 100})
>>> registry.has("key1")
True
>>> value = registry.unregister("key1")
>>> value
42
>>> registry.has("key1")
False
Clearing Registry¶
Remove all entries from the registry:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]({"key1": 42, "key2": 100})
>>> len(registry)
2
>>> registry.clear()
>>> len(registry)
0
Iterating Over Registry¶
Access keys, values, and items:
>>> from coola.registry import Registry
>>> registry = Registry[str, int]({"a": 1, "b": 2})
>>> list(registry.keys())
['a', 'b']
>>> list(registry.values())
[1, 2]
>>> list(registry.items())
[('a', 1), ('b', 2)]
Equality Checking¶
Compare two registries:
>>> from coola.registry import Registry
>>> registry1 = Registry[str, int]({"key1": 42, "key2": 100})
>>> registry2 = Registry[str, int]({"key1": 42, "key2": 100})
>>> registry3 = Registry[str, int]({"key1": 42})
>>> registry1.equal(registry2)
True
>>> registry1.equal(registry3)
False
TypeRegistry (Type-Based)¶
Basic Usage¶
The TypeRegistry class provides a thread-safe container for mapping Python types to values:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[str]()
>>> registry.register(int, "I am an integer")
>>> registry.get(int)
'I am an integer'
Type Resolution with MRO¶
The key feature of TypeRegistry is its resolve() method, which uses the Method Resolution Order
(MRO) to find the most appropriate value for a type:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[str]()
>>> registry.register(object, "I am an object")
>>> registry.register(int, "I am an integer")
>>> # Direct match
>>> registry.resolve(int)
'I am an integer'
>>> # Falls back to parent type via MRO (bool inherits from int)
>>> registry.resolve(bool)
'I am an integer'
>>> # Falls back to object
>>> registry.resolve(str)
'I am an object'
Dictionary-Style Operations¶
Like Registry, TypeRegistry supports standard dictionary operations:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[str]()
>>> registry[str] = "I am a string"
>>> str in registry
True
>>> registry[str]
'I am a string'
>>> del registry[str]
Initialization with Types¶
Initialize with type mappings:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[int]({str: 100, float: 200})
>>> len(registry)
2
>>> registry.get(str)
100
Safe Type Registration¶
Prevent accidental overwriting of type mappings:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[int]()
>>> registry.register(str, 42)
>>> registry.register(str, 100) # doctest: +SKIP
Traceback (most recent call last):
...
RuntimeError: A value is already registered for <class 'str'>...
Allow overwriting with exist_ok=True:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[int]()
>>> registry.register(str, 42)
>>> registry.register(str, 100, exist_ok=True)
>>> registry.get(str)
100
Bulk Type Registration¶
Register multiple types at once:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[str]()
>>> registry.register_many({int: "integer", float: "float", str: "string"})
>>> len(registry)
3
>>> registry.get(float)
'float'
Inheritance-Based Lookup¶
The resolve() method walks the MRO to find the most specific registered type:
>>> from coola.registry import TypeRegistry
>>> class Animal:
... pass
...
>>> class Dog(Animal):
... pass
...
>>> class Poodle(Dog):
... pass
...
>>> registry = TypeRegistry[str]()
>>> registry.register(Animal, "animal")
>>> registry.register(Dog, "dog")
>>> registry.resolve(Dog)
'dog'
>>> registry.resolve(Poodle) # Resolves to parent Dog
'dog'
Performance Optimization¶
The TypeRegistry uses an internal cache for type resolution to optimize performance:
>>> from coola.registry import TypeRegistry
>>> registry = TypeRegistry[str]()
>>> registry.register(object, "base")
>>> registry.register(int, "integer")
>>> # First resolve() populates cache
>>> registry.resolve(bool)
'integer'
>>> # Subsequent resolves use cached result
>>> registry.resolve(bool)
'integer'
The cache is automatically cleared when you register or unregister types to ensure consistency.
Thread Safety¶
Both Registry and TypeRegistry are thread-safe, using reentrant locks (threading.RLock) to
protect concurrent access:
>>> from coola.registry import Registry
>>> import threading
>>> registry = Registry[str, int]()
>>> def worker(key, value):
... registry.register(key, value, exist_ok=True)
...
>>> threads = [threading.Thread(target=worker, args=(f"key{i}", i)) for i in range(10)]
>>> for t in threads:
... t.start()
...
>>> for t in threads:
... t.join()
...
>>> len(registry) == 10
True
Common Use Cases¶
Plugin System¶
Use a registry to manage plugins:
>>> from coola.registry import Registry
>>> plugin_registry = Registry[str, type]()
>>> class JSONParser:
... pass
...
>>> class XMLParser:
... pass
...
>>> plugin_registry.register("json", JSONParser)
>>> plugin_registry.register("xml", XMLParser)
>>> parser_class = plugin_registry.get("json")
>>> parser_class.__name__
'JSONParser'
Type Dispatch¶
Use a type registry for dispatching based on type:
>>> from coola.registry import TypeRegistry
>>> handlers = TypeRegistry[str]()
>>> handlers.register(int, "handle_int")
>>> handlers.register(str, "handle_str")
>>> handlers.register(list, "handle_list")
>>> handlers.resolve(int)
'handle_int'
>>> handlers.resolve(bool) # bool inherits from int
'handle_int'
Factory Pattern¶
Use a registry to implement a factory pattern:
>>> from coola.registry import Registry
>>> factory = Registry[str, type]()
>>> class Circle:
... pass
...
>>> class Square:
... pass
...
>>> factory.register("circle", Circle)
>>> factory.register("square", Square)
>>> shape_class = factory.get("circle")
>>> shape_class.__name__
'Circle'
Configuration Management¶
Store configuration handlers:
>>> from coola.registry import Registry
>>> config_handlers = Registry[str, callable]()
>>> config_handlers.register("database", lambda: {"host": "localhost"})
>>> config_handlers.register("cache", lambda: {"enabled": True})
>>> db_config = config_handlers.get("database")()
>>> db_config
{'host': 'localhost'}
Design Principles¶
The coola.registry package design provides:
- Thread safety: All operations are protected by locks for concurrent access
- Type safety: Generic typing support for type hints and IDE support
- Flexibility: Support for both generic key-value and type-based registries
- Performance: Caching in
TypeRegistryfor efficient type resolution - Standard interface: Dictionary-like operations for familiar API
Comparison: Registry vs TypeRegistry¶
| Feature | Registry | TypeRegistry |
|---|---|---|
| Key type | Any hashable | Python types only |
| MRO lookup | No | Yes (via resolve()) |
| Caching | No | Yes (for type resolution) |
| Use case | Generic key-value storage | Type-based dispatch |
See Also¶
coola.recursive: UsesTransformerRegistry(aTypeRegistry) internallycoola.iterator: Uses registry pattern for iterator dispatchcoola.random: UsesRegistryto manage random managers