Invoking functions and class-constructors from a string is a common design pattern
that AutoRegistry aims to solve.
For example, a user might specify a backend of type "sqlite"
in a yaml configuration
file, for which our program needs to construct the SQLite
subclass of our Database
class.
Classically, you would need to manually create a lookup, mapping the string "sqlite"
to
the SQLite
constructor.
With AutoRegistry, the lookup is automatically created for you.
AutoRegistry has a single powerful class Registry
that can do the following:
- Be inherited to automatically register subclasses by their name.
- Be directly invoked
my_registry = Registry()
to create a decorator for registering callables like functions. - Traverse and automatically create registries for other python libraries.
AutoRegistry is also highly configurable, with features like name-schema-enforcement and name-conversion-rules. Checkout the docs for more information.
AutoRegistry requires Python >=3.8
.
python -m pip install autoregistry
Registry
adds a dictionary-like interface to class constructors
for looking up subclasses.
from abc import abstractmethod
from dataclasses import dataclass
from autoregistry import Registry
@dataclass
class Pokemon(Registry):
level: int
hp: int
@abstractmethod
def attack(self, target):
"""Attack another Pokemon."""
class Charmander(Pokemon):
def attack(self, target):
return 1
class Pikachu(Pokemon):
def attack(self, target):
return 2
class SurfingPikachu(Pikachu):
def attack(self, target):
return 3
print(f"{len(Pokemon)} Pokemon types registered:")
print(f" {list(Pokemon)}")
# By default, lookup is case-insensitive
charmander = Pokemon["cHaRmAnDer"](level=7, hp=31)
print(f"Created Pokemon: {charmander}")
This code block produces the following output:
3 Pokemon types registered: ['charmander', 'pikachu', 'surfingpikachu'] Created Pokemon: Charmander(level=7, hp=31)
Directly instantiating a Registry
object allows you to
register functions by decorating them.
from autoregistry import Registry
pokeballs = Registry()
@pokeballs
def masterball(target):
return 1.0
@pokeballs
def pokeball(target):
return 0.1
for ball in ["pokeball", "masterball"]:
success_rate = pokeballs[ball](None)
print(f"Ash used {ball} and had {success_rate=}")
This code block produces the following output:
Ash used pokeball and had success_rate=0.1
Ash used masterball and had success_rate=1.0
Create a registry for another python module.
import torch
from autoregistry import Registry
optims = Registry(torch.optim)
# "adamw" and ``lr`` could be coming from a configuration file.
optimizer = optims["adamw"](model.parameters(), lr=3e-3)
assert list(optims) == [
"asgd",
"adadelta",
"adagrad",
"adam",
"adamw",
"adamax",
"lbfgs",
"nadam",
"optimizer",
"radam",
"rmsprop",
"rprop",
"sgd",
"sparseadam",
"lr_scheduler",
"swa_utils",
]