CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-import-tracker

A tool for managing dependencies in a modular python project by tracking which dependencies are needed by which sub-modules

Pending
Overview
Eval results
Files

lazy-import-errors.mddocs/

Lazy Import Errors

Context manager and function for deferring ImportError exceptions until modules are actually used, preventing crashes from missing optional dependencies during import time.

Capabilities

Lazy Import Error Context Manager

Enables lazy import error handling that captures ImportError exceptions and defers them until the module is actually used, allowing imports of optional dependencies to succeed even when the dependency is not installed.

def lazy_import_errors(
    *,
    get_extras_modules: Optional[Callable[[], Set[str]]] = None,
    make_error_message: Optional[Callable[[str], str]] = None,
):
    """
    Enable lazy import errors.
    
    When enabled, lazy import errors will capture imports that would otherwise
    raise ImportErrors and defer those errors until the last possible moment
    when the functionality is needed.
    
    This function may be used either as a function directly or as a
    contextmanager which will disable lazy errors upon exit.
    
    Args:
        get_extras_modules: Optional callable that fetches the list of module names
                           that are managed as extras using setup_tools.parse_requirements.
                           (Mutually exclusive with make_error_message)
        make_error_message: Optional callable that takes the name of the module which
                           failed to import and returns an error message string.
                           (Mutually exclusive with get_extras_modules)
    """

Global Lazy Import Errors

Enable lazy import errors globally for all subsequent imports:

from import_tracker import lazy_import_errors

# Enable globally - all failed imports will be deferred
lazy_import_errors()

# Now these imports won't crash even if modules are missing
import optional_module
from some_package import maybe_missing_submodule

# Error will be raised only when you try to use the module
try:
    optional_module.some_function()  # ModuleNotFoundError raised here
except ModuleNotFoundError as e:
    print(f"Module not available: {e}")

Context Manager Usage

Use as a context manager to enable lazy errors only for specific imports:

from import_tracker import lazy_import_errors

# Required imports - will fail immediately if missing
import requests
import json

# Optional imports - errors deferred until usage
with lazy_import_errors():
    import matplotlib.pyplot as plt
    import seaborn as sns
    from some_package import optional_feature

# These will work fine even if matplotlib/seaborn are not installed
print("Imports completed successfully")

# Error raised only when trying to use missing modules
try:
    plt.plot([1, 2, 3])  # ModuleNotFoundError raised here if matplotlib missing
except ModuleNotFoundError:
    print("Matplotlib not available, using basic plotting")

Custom Error Messages

Provide custom error messages for missing dependencies:

def custom_error_message(module_name: str) -> str:
    return f"Missing optional dependency: {module_name}. Install with: pip install {module_name}"

with lazy_import_errors(make_error_message=custom_error_message):
    import some_optional_module

# When used, will show custom message:
# ModuleNotFoundError: Missing optional dependency: some_optional_module. Install with: pip install some_optional_module

Extras Integration

Integration with setuptools extras_require for helpful installation messages:

def get_extras_modules():
    return {'my_package.plotting', 'my_package.advanced_features'}

with lazy_import_errors(get_extras_modules=get_extras_modules):
    from my_package import plotting
    
# When used within an extras module, provides installation instructions:
# ModuleNotFoundError: No module named 'matplotlib'. 
# To install the missing dependencies, run `pip install my_package[my_package.plotting]`

Practical Usage Patterns

Optional Dependency Handling

from import_tracker import lazy_import_errors

# Enable lazy errors for optional dependencies
with lazy_import_errors():
    try:
        import pandas as pd
        HAS_PANDAS = True
    except ImportError:
        HAS_PANDAS = False

def process_data(data):
    if HAS_PANDAS:
        return pd.DataFrame(data).describe()
    else:
        return {"error": "pandas not available"}

Hierarchical Wild Imports

Particularly useful for packages with hierarchical wild imports:

# In my_package/__init__.py
from import_tracker import lazy_import_errors

# Enable lazy errors globally for this package
lazy_import_errors()

# These won't crash the entire package if submodules have missing dependencies
from .core import *
from .utils import *
from .optional_features import *  # Won't crash if dependencies missing

Decorator Compatibility

Lazy errors work with decorators from missing dependencies:

with lazy_import_errors():
    from some_optional_package import optional_decorator

# This works even if some_optional_package is not installed
@optional_decorator  
def my_function():
    pass

# Error only raised when decorator functionality is actually invoked

Internal Implementation

The lazy import error system is implemented using several internal classes:

Meta Path Finder System

class _LazyErrorMetaFinder(importlib.abc.MetaPathFinder):
    """Meta path finder for intercepting import failures"""
    
    def find_spec(self, fullname, path, *args, **kwargs):
        """Returns lazy loader spec for missing modules"""

class _LazyErrorLoader(importlib.abc.Loader):
    """Loader for creating lazy error modules"""
    
    def create_module(self, spec):
        """Creates _LazyErrorModule instances"""
    
    def exec_module(self, *_, **__):
        """No-op execution"""

Lazy Error Objects

class _LazyErrorModule(ModuleType):
    """Module that defers ImportError until attribute access"""
    
    def __getattr__(self, name: str) -> _LazyErrorAttr:
        """Returns lazy error attributes"""

class _LazyErrorAttr(type):
    """Lazy error object that raises on any meaningful usage"""
    
    def __call__(self, *_, **__):
        """Handles decorator usage at import time"""
    
    def __getattr__(self, name: str) -> "_LazyErrorAttr":
        """Recursive attribute access"""

Error Behavior

Import Time vs Usage Time

# Import time - no error raised
with lazy_import_errors():
    import missing_module

# Usage time - error raised on meaningful operations
missing_module.function()        # Raises ModuleNotFoundError
str(missing_module)             # Raises ModuleNotFoundError  
missing_module == something     # Returns False (import time operation)
missing_module.attr.nested      # Returns another lazy error object

Shell Environment Note

When using extras integration, be aware of shell escaping requirements:

# In bash/sh
pip install my_package[my_package.feature]

# In zsh - requires escaping
pip install my_package\[my_package.feature\]

Types

from typing import Callable, Optional, Set
from contextlib import AbstractContextManager

# Function parameter types
GetExtrasModulesFunc = Callable[[], Set[str]]
MakeErrorMessageFunc = Callable[[str], str]

# Context manager type
LazyImportErrorContext = AbstractContextManager[None]

Install with Tessl CLI

npx tessl i tessl/pypi-import-tracker

docs

dependency-tracking.md

index.md

lazy-import-errors.md

setup-tools.md

tile.json