Configuration

Configuring Inheritence-Based

When inheriting from the Registry class, keyword configuration values can be passed along side it when defining the subclass. For example:

class Pokemon(Registry, case_sensitive=True):
    pass

Each subclass registry will copy the configuration of its parent, and update it with newly passed in values. For example:

class Pokemon(Registry, suffix="Type", recursive=False):
    pass


class RockType(Pokemon, suffix=""):
    pass


class Geodude(RockType):
    pass


# it's just "rock" instead of "rocktype" because we strip the suffix by default.
geodude = Pokemon["rock"]["geodude"]()

All direct children of Pokemon MUST end with "Type". Children of RockType will NOT be registered with RockType's parent, Pokemon because recursive=False is set. For RockType, setting suffix="" overrides its parent's suffix setting, allowing the definition of the subclass Geodude, despite it not ending with "Type".

Configuring Decorator-Based

When directly declaring a Registry, configurations are passed as keyword arguments when instantiating the Registry object:

readers = Registry(suffix="_read")


@readers
def yaml_read(fn):
    pass


@readers()  # This also works.
def json_read(fn):
    pass


# it's just "json" instead of "json_read" because we strip the suffix by default.
data = readers["json"]("my_file.json")

Name Override and Aliases

There are two special configuration values: name and aliases. name overrides the auto-derived string to register the class/function under, while aliases registers additional string(s) to the class/function, but doesn't impact the auto-derived registration key. aliases may be a single string, or a list of strings. name and aliases values are not subject to configured naming rules and will not be modified by configurations, such as strip_suffix. However, values are still subject to the overwrite configuration and will raise KeyCollisionError if name or aliases attempts to overwrite an existing entry while overwrite=False. These parameters are intended to aid developers maintain backwards compatibility as their codebase changes.

Inheritence-Based

Name and aliases are provided as additional class keyword arguments.

class Pokemon(Registry):
    pass


class Ekans(name="snake"):
    pass


class Pikachu(aliases=["electricmouse"]):
    pass


my_pokemon = []
# Pokemon["ekans"] will raise a KeyError
my_pokemon.append(Pokemon["snake"]())
my_pokemon.append(Pokemon["pikachu"]())
my_pokemon.append(Pokemon["electricmouse"]())

Decorator-Based

Name and aliases are provided as additional decorator keyword arguments.

registry = Registry()


@registry(name="foo")
def foo2():
    pass


@registry(aliases=["baz", "bop"])
def bar():
    pass


assert list(registry) == ["foo", "bar", "baz", "bop"]

Configurations

This section describes and provides examples for all of the configurable options in autoregistry.

case_sensitive: bool = False

If True, all lookups are case-sensitive. Otherwise, all lookups are case-insensitive. A failed lookup will result in a KeyError.

class Pokemon(Registry, case_sensitive=False):
    pass


class Pikachu(Pokemon):
    pass


class SurfingPikachu(Pokemon):
    pass


assert list(Pokemon) == ["pikachu", "surfingpikachu"]
assert list(Pikachu) == ["surfingpikachu"]
pikachu = Pokemon["piKaCHu"]()
class Pokemon(Registry, case_sensitive=True):
    pass


class Pikachu(Pokemon):
    pass


class SurfingPikachu(Pokemon):
    pass


assert list(Pokemon) == ["Pikachu", "SurfingPikachu"]
assert list(Pikachu) == ["SurfingPikachu"]
pikachu = Pokemon["Pikachu"]()

# This will raise a KeyError due to the lowercase "p".
pikachu = Pokemon["pikachu"]()

regex: str = ""

Registered items MUST match this regular expression. If a registered item does NOT match this regex, InvalidNameError will be raised.

# Capital letters only
registry = Registry(regex="[A-Z]+", case_sensitive=True)


@registry
def FOO():
    pass


# This will raise an InvalidNameError, because the supplied regex only allows for capital letters.
@registry
def bar():
    pass


assert list(registry) == ["FOO"]

prefix: str = ""

Registered items MUST start with this prefix. If a registered item does NOT start with this prefix, InvalidNameError will be raised.

class Sensor(Registry, prefix="Sensor"):
    pass


# This will raise an InvalidNameError because the class name doesn't start with "Sensor"
class Temperature(Sensor):
    pass


class SensorTemperature(Sensor):
    pass

suffix: str = ""

Registered items MUST end with this suffix. If a registered item does NOT end with this suffix, InvalidNameError will be raised.

class Sensor(Registry, suffix="Sensor"):
    pass


# This will raise an InvalidNameError because the class name doesn't end with "Sensor"
class Temperature(Sensor):
    pass


class TemperatureSensor(Sensor):
    pass

strip_prefix: bool = True

If True, the prefix will be removed from registered items. This generally allows for a more natural lookup.

class Sensor(Registry, prefix="Sensor", strip_prefix=True):
    pass


class SensorTemperature(Sensor):
    pass


class SensorHumidity(Sensor):
    pass


assert list(Sensor) == ["temperature", "humidity"]
my_temperature_sensor = Sensor["temperature"]()

strip_suffix: bool = True

If True, the suffix will be removed from registered items. This generally allows for a more natural lookup.

class Sensor(Registry, suffix="Sensor", strip_suffix=True):
    pass


class TemperatureSensor(Sensor):
    pass


class HumiditySensor(Sensor):
    pass


assert list(Sensor) == ["temperature", "humidity"]
my_temperature_sensor = Sensor["temperature"]()

register_self: bool = False

If True, each registry class is registered in its own registry.

class Pokeball(Registry, register_self=True):
    def probability(self, target):
        return 0.2


class Masterball(Pokeball):
    def probability(self, target):
        return 1.0


assert list(Pokeball) == ["pokeball", "masterball"]

recursive: bool = True

If True, all subclasses will be recursively registered to their parents. If registering a module, this means all submodules will be recursively traversed.

class Pokemon(Registry, recursive=True):
    pass


class Pikachu(Pokemon):
    pass


class SurfingPikachu(Pokemon):
    pass


assert list(Pokemon) == ["pikachu", "surfingpikachu"]
assert list(Pikachu) == ["surfingpikachu"]
class Pokemon(Registry, recursive=False):
    pass


class Pikachu(Pokemon):
    pass


class SurfingPikachu(Pokemon):
    pass


assert list(Pokemon) == ["pikachu"]
assert list(Pikachu) == ["surfingpikachu"]

snake_case: bool = False

By default, for case-insensitive queries, the key is derived by taking the all-lowercase version of the class name. If snake_case=True, the PascalCase class names will be instead converted to snake_case.

Snake case conversion is performed after name validation (like prefix and suffix) checks are performed.

class Tools(Registry, snake_case=True):
    pass


class Hammer(Tools):
    pass


class SocketWrench(Tools):
    pass


assert list(Tools) == ["hammer", "socket_wrench"]

overwrite: bool = False

If overwrite=False, attempting to register an object that would overwrite an existing registered item would result in a KeyCollisionError. If overwrite=True, then the previous entry will be overwritten and no exception will be raised.

registry = Registry()


@registry
def foo():
    pass


# This will raise a ``KeyCollisionError``
@registry
def foo():
    pass
registry = Registry(overwrite=True)


@registry
def foo():
    return 1


@registry
def foo():
    return 2


assert registry["foo"]() == 2