"""Dynamic registry for Machine subclasses.Machines self-register via the :func:`register` decorator. Third-party packagescan add new machine types by applying the same decorator and advertising theclass through the ``gcode_reader.machines`` entry-point group in their``pyproject.toml``:: [project.entry-points."gcode_reader.machines"] acme = "acme_gcode:AcmePrinter"The registry is populated at import time; call :func:`load_entry_points` once(e.g. in ``gcode_reader/__init__.py``) to pull in installed third-party machines."""from__future__importannotationsfromimportlib.metadataimportentry_pointsfromtypingimportTYPE_CHECKINGifTYPE_CHECKING:from.machineimportMachine_registry:dict[str,type[Machine]]={}
[docs]defregister(name:str,aliases:list[str]|None=None):"""Class decorator that registers a Machine subclass by name. Args: name: Primary key used to look up the machine. aliases: Optional additional keys that resolve to the same class. Example:: @register("acme", aliases=["acme_v2"]) class AcmePrinter(Machine): ... """defdecorator(cls:type[Machine])->type[Machine]:_registry[name]=clsforaliasinaliasesor[]:_registry[alias]=clsreturnclsreturndecorator
[docs]defget(name:str)->type[Machine]:"""Return the Machine subclass registered under *name*. Args: name: Key or alias (case-insensitive, spaces and underscores ignored). Raises: ValueError: If *name* is not found in the registry. """key=name.lower().replace(" ","").replace("_","")ifkeynotin_registry:available=sorted(set(_registry))raiseValueError(f"Machine '{name}' not recognised. Available machines:\n\t{available}")return_registry[key]
[docs]deflist_machines()->dict[str,type[Machine]]:"""Return a copy of the full registry mapping (names + aliases -> classes)."""returndict(_registry)
[docs]defload_entry_points()->None:"""Discover and import third-party machines registered via entry points. Call this once at package initialisation to pick up machines shipped by external packages. Safe to call multiple times. """forepinentry_points(group="gcode_reader.machines"):ep.load()