from__future__importannotationsimportdataclassesimportsysimporttypingfromdataclassesimportdataclassfromtypingimportCallable,ClassVar,Type,TypeVarR=TypeVar("R",bound="Registrable")ifsys.version_info<(3,11):# HACK: work-around for https://stackoverflow.com/q/70400639/4151392dataclasses.InitVar.__call__=lambda*_:None# type: ignore
[docs]@dataclassclassRegistrable:_registry:ClassVar[dict[str,Type[Registrable]]]_default_type:ClassVar[str|None]registered_name:ClassVar[str|None]registered_base:ClassVar[Type[Registrable]|None]type:dataclasses.InitVar[str|None]=dataclasses.field(default=None,kw_only=True,repr=False)def__new__(cls,*args,type:str|None=None,**kwargs):delargs,kwargsiftypeisnotNoneandtype!=cls.registered_name:iftypenotincls._registry:raiseKeyError(f"'{type}' is not registered name for {cls.__name__}. "f"Available choices are: {list(cls._registry.keys())}")returnsuper().__new__(cls._registry[type])elifcls._default_typeisnotNoneandcls.registered_nameisNone:returnsuper().__new__(cls._registry[cls._default_type])else:returnsuper().__new__(cls)def__init_subclass__(cls,**kwargs):super().__init_subclass__(**kwargs)cls._registry={}cls._default_type=Noneifnothasattr(cls,"registered_name"):cls.registered_name=Noneifnothasattr(cls,"registered_base"):cls.registered_base=None@classmethoddefregister(cls,name:str,default:bool=False)->Callable[[Type[R]],Type[R]]:ifclsisRegistrable:raiseTypeError("Cannot register with the base Registrable class itself")defregister_subclass(subclass:Type[R])->Type[R]:ifsubclassisRegistrable:raiseTypeError("Cannot register the base Registrable class itself")ifnotissubclass(subclass,cls):raiseTypeError(f"class {subclass.__name__} must be a subclass of {cls.__name__} in order to register it")ifnotdataclasses.is_dataclass(subclass):raiseTypeError(f"class {subclass.__name__} must be a dataclass in order to register it")ifdefault:ifcls._default_typeisnotNone:raiseValueError(f"A default implementation for {cls.__name__} has already been registered")else:cls._default_type=namecls._registry[name]=subclasssubclass.registered_name=namesubclass.registered_base=clsreturnsubclass# type: ignorereturnregister_subclass@classmethoddefget_registered_name(cls:Type[R],subclass:Type[R]|None=None)->str:ifclsisRegistrable:raiseTypeError("Cannot register the base Registrable class itself")ifsubclassisNone:ifcls.registered_nameisnotNone:returncls.registered_nameelse:raiseValueError(f"class {cls.__name__} is not a registered subclass of any base registrable class")forname,registered_subclassincls._registry.items():ifregistered_subclass==subclass:returnnameraiseValueError(f"class {subclass.__name__} is not a registered subclass of {cls.__name__}")@classmethoddefget_registered_class(cls:Type[R],type:str)->Type[R]:ifclsisRegistrable:raiseTypeError("Cannot register with the base Registrable class itself")iftypenotincls._registry:raiseKeyError(f"'{type}' is not registered name for {cls.__name__}. "f"Available choices are: {cls.get_registered_names()}")returntyping.cast(Type[R],cls._registry[type])@classmethoddefget_registered_names(cls)->list[str]:ifclsisRegistrable:raiseTypeError("Cannot register with the base Registrable class itself")returnlist(cls._registry.keys())@classmethoddefget_default(cls:Type[R])->Type[R]:ifclsisRegistrable:raiseTypeError("Cannot register with the base Registrable class itself")ifcls._default_typeisNone:raiseValueError(f"A default implementation of {cls.__name__} has not been registered")returncls.get_registered_class(cls._default_type)