Source code for rail.projects.dynamic_class

from __future__ import annotations

from typing import Any, TypeVar

T = TypeVar("T", bound="DynamicClass")


[docs] class DynamicClass: """Base class for classes that will create objects of sub-classes dynamically This implements: 1. keeping track of all the loaded sub-classes, 2. being able to load a new sub-class from the python module and class name 3. being able to create an object of sub-class from a dict Subclasses that serve as parent classes to several addtional sub-classes should 1. override the sub_classes to keep track of their sub-classes """ sub_classes: dict[str, type[DynamicClass]] = {} def __init_subclass__(cls) -> None: cls.sub_classes[cls.__name__] = cls
[docs] @classmethod def print_classes(cls) -> None: """Print the sub-classes of DynamicClass that have been loaded""" print(f"{cls.__name__}") for key, val in cls.sub_classes.items(): print(f" {key} {val}")
[docs] @classmethod def get_sub_class(cls: type[T], key: str, class_name: str | None = None) -> type[T]: """Get a particular sub-class of DynamicClass by name Parameters ---------- key: str Key for the subclass. class_name: str | None = None Full class name, so it can be loaded Returns ------- type: Subclass in question """ if key in cls.sub_classes: sub_class = cls.sub_classes[key] assert issubclass(sub_class, cls) return sub_class if class_name is None: raise KeyError( f"class {key} not found in {list(cls.sub_classes.keys())} and class_name is not provided." ) return cls.load_sub_class(class_name)
[docs] @classmethod def load_sub_class(cls: type[T], class_name: str) -> type[T]: """Import a particular sub-class of DynamicClass by name Parameters ---------- class_name: str Full path and name of the subclass, e.g., rail.plotting.some_file.SomeClass Returns ------- type: Subclass in question """ tokens = class_name.split(".") module = ".".join(tokens[:-1]) key = tokens[-1] __import__(module) sub_class = cls.get_sub_class(key) assert issubclass(sub_class, cls) return sub_class
[docs] @classmethod def create_from_dict( cls: type[T], config_dict: dict[str, Any], ) -> T: """Create a DynamicClass object of type T Parameters ---------- config_dict: dict[str, Any], Configuration parameters Returns ------- T: Newly created object Notes ----- config_dict must include 'class_name' which gives the path and name of the class, e.g., rail.plotters.some_file.SomeClass """ copy_config = config_dict.copy() class_name = copy_config.pop("class_name") tokens = class_name.split(".") key = tokens[-1] sub_class = cls.get_sub_class(key, class_name) assert issubclass(sub_class, cls) return sub_class(**copy_config)