Skip to content

Automatic registry design-pattern library for mapping names to functionality.

License

Notifications You must be signed in to change notification settings

BrianPugh/autoregistry

Repository files navigation

https://raw.githubusercontent.com/BrianPugh/autoregistry/main/assets/logo_400w.png

Python compat PyPi GHA Status Coverage Documentation Status

AutoRegistry

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.

Watch AutoRegistry in action!

Installation

AutoRegistry requires Python >=3.8.

python -m pip install autoregistry

Examples

Class Inheritance

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)

Function Registry

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

Module Registry

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",
]