← Today I Learned

Verify and lazy load imports of a python package

Scenario: You have a python package with an entry point class that needs to be instantiated and setup before other classes in the package can be imported and used.

Solution: Intercept the getattr method of the package’s __init__.py file to verify and lazy load the other classes in the package.

__init__.py

# serverless

# Import entry point class
from .adr import ADR

# Expose entry point class
__all__ = ["ADR"]


# Lazy import mechanism for dependent classes
class _LazyLoader:
    """
    Lazy loader for dependent classes to defer imports until accessed.
    This enforces ADR.setup() to be called before triggering the imports.
    """

    def __init__(self):
        self._initialized = False

    def _initialize(self):
        """Perform the actual imports when accessed for the first time."""
        # Ensure ADR has been set up
        ADR.ensure_setup()

        # Define the modules to be imported
        item_classes = (
            "Item",
            "HTML",
            "Animation",
            ...
        )
        # Dynamically import classes from item and template using importlib
        import importlib

        item_module = importlib.import_module(".item", package=__package__)

        # Dynamically add these to the module namespace and __all__
        for cls_name in item_classes:
            cls = getattr(item_module, cls_name, None)
            if cls:
                globals()[cls_name] = cls
                __all__.append(cls_name)

        self._initialized = True

    def __getattr__(self, name):
        if not self._initialized:
            self._initialize()
        return globals()[name]


# Create a lazy loader instance
_lazy_loader = _LazyLoader()


# Define a custom module-level __getattr__ to defer imports
def __getattr__(name):
    return getattr(_lazy_loader, name)

adr.py

class ADR:
    # Class-level variable to track the active instance
    _curr_instance = None

    def __init__(
        self,
        ...
    ) -> None:
        ...
        self.is_setup = False
        ADR._curr_instance = self  # Set this as the current active instance

    def setup(self):
        """
        Perform the setup operations for the ADR instance.
        """
        ... # Perform setup operations
        self.is_setup = True

    @classmethod
    def ensure_setup(cls):
        """
        Check if the current ADR instance has been set up.
        Raise an ImportError if not.
        """
        if not cls._curr_instance or not cls._curr_instance.is_setup:
            raise ImportError(
                "ADR has not been set up yet. Please create an ADR instance and call its `setup()` method "
                "before importing and using other classes."
            )