Getting Started
As you saw in the introduction, you can create a new registry using the
class_registry.ClassRegistry
class.
ClassRegistry
defines a register
method that you can use as a decorator
to add classes to the registry:
from class_registry import ClassRegistry
pokedex = ClassRegistry()
@pokedex.register('fire')
class Charizard:
pass
Once you’ve registered a class, you can then create a new instance using the corresponding registry key:
sparky = pokedex['fire']
assert isinstance(sparky, Charizard)
Note in the above example that sparky
is an instance of Charizard
.
If you try to access a registry key that has no classes registered, it will raise a
class_registry.RegistryKeyError
:
from class_registry import RegistryKeyError
try:
tex = pokedex['spicy']
except RegistryKeyError:
pass
Typed Registries
If a ClassRegistry
always returns objects derived from a particular
base class, you can provide a
type parameter
to help with type checking, autocomplete, etc.:
# Add type parameter ``[Pokemon]``:
pokedex = ClassRegistry[Pokemon]()
# Your IDE will automatically infer that ``fighter1`` is a ``Pokemon``.
fighter1 = pokedex['fire']
Auto-Detecting Registry Keys
By default, you have to provide the registry key whenever you register a new class. But, there’s an easier way to do it!
When you initialise your ClassRegistry
, provide an attr_name
parameter.
When you register new classes, your registry will automatically extract the registry key
using that attribute:
pokedex = ClassRegistry('element')
@pokedex.register
class Squirtle:
element = 'water'
beauregard = pokedex['water']
assert isinstance(beauregard, Squirtle)
Note in the above example that the registry automatically extracted the registry key for
the Squirtle
class using its element
attribute.
Collisions
What happens if two classes have the same registry key?
pokedex = ClassRegistry('element')
@pokedex.register
class Bulbasaur:
element = 'grass'
@pokedex.register
class Ivysaur:
element = 'grass'
janet = pokedex['grass']
assert isinstance(janet, Ivysaur)
As you can see, if two (or more) classes have the same registry key, whichever one is registered last will override any of the other(s).
Note
It is not always easy to predict the order in which classes will be registered, especially when they are spread across different modules, so you probably don’t want to rely on this behaviour!
If you want to prevent collisions, you can pass unique=True
to the
ClassRegistry
initialiser to raise an exception whenever a collision occurs:
from class_registry import RegistryKeyError
pokedex = ClassRegistry('element', unique=True)
@pokedex.register
class Bulbasaur:
element = 'grass'
try:
@pokedex.register
class Ivysaur:
element = 'grass'
except RegistryKeyError:
pass
janet = pokedex['grass']
assert isinstance(janet, Bulbasaur)
Because we passed unique=True
to the ClassRegistry
initialiser,
attempting to register Ivysaur
with the same registry key as Bulbasaur
raised a
RegistryKeyError
, so it didn’t override Bulbasaur
.
Init Params
Every time you access a registry key in a ClassRegistry
, it creates a new
instance:
marlene = pokedex['grass']
charlene = pokedex['grass']
assert marlene is not charlene
Since you’re creating a new instance every time, you also have the option of providing
args and kwargs to the class initialiser using the registry’s get()
method:
pokedex = ClassRegistry('element')
@pokedex.register
class Caterpie:
element = 'bug'
def __init__(self, level=1):
super(Caterpie, self).__init__()
self.level = level
timmy = pokedex.get('bug')
assert timmy.level == 1
tommy = pokedex.get('bug', 16)
assert tommy.level == 16
tammy = pokedex.get('bug', level=42)
assert tammy.level == 42
Any arguments that you provide to get()
will be passed directly to the
corresponding class’ initialiser.
Hint
You can create a service registry that always returns the same instance per registry
key by wrapping it in a ClassRegistryInstanceCache
. See
Creating Service Registries for more information.