Source code for next.components.loading

"""LRU cache and loader for `component.py` modules.

`ModuleCache` keeps a bounded mapping of file paths to loaded modules
so repeated renders do not re-execute module top level on every
request. `ModuleLoader` wraps the cache and falls back to reading the
file when the entry is missing.
"""

from __future__ import annotations

import importlib.util
import logging
from collections import OrderedDict
from typing import TYPE_CHECKING, cast


if TYPE_CHECKING:
    from pathlib import Path
    from types import ModuleType


logger = logging.getLogger(__name__)

_CACHE_MISS = object()


[docs] class ModuleCache: """Remembers loaded Python modules by file path and drops the oldest when full."""
[docs] def __init__(self, maxsize: int = 128) -> None: """Create an LRU cache with the given capacity.""" self._maxsize = maxsize self._order: OrderedDict[Path, ModuleType | None] = OrderedDict()
[docs] def get(self, path: Path) -> ModuleType | None | object: """Return the cached module, a cached `None`, or the miss sentinel.""" if path not in self._order: return _CACHE_MISS self._order.move_to_end(path) return self._order[path]
[docs] def set(self, path: Path, module: ModuleType | None) -> None: """Store the module (or `None` on failure) under `path`, evicting when full.""" if path not in self._order and len(self._order) >= self._maxsize: self._order.popitem(last=False) self._order[path] = module self._order.move_to_end(path)
[docs] def clear(self) -> None: """Drop every entry from the cache.""" self._order.clear()
[docs] def __len__(self) -> int: """Return the number of cached entries.""" return len(self._order)
[docs] def __contains__(self, path: Path) -> bool: """Return True when `path` currently has a cached entry.""" return path in self._order
[docs] class ModuleLoader: """Loads a `.py` file as a module and reuses the last load for the same path."""
[docs] def __init__(self, cache: ModuleCache | None = None) -> None: """Bind the loader to a shared or new `ModuleCache`.""" self._cache = cache or ModuleCache()
[docs] def load(self, path: Path) -> ModuleType | None: """Return the module for `path`, loading it on cache miss.""" cached = self._cache.get(path) if cached is _CACHE_MISS: module = self._load_from_disk(path) self._cache.set(path, module) return module return cast("ModuleType | None", cached)
def _load_from_disk(self, path: Path) -> ModuleType | None: try: spec = importlib.util.spec_from_file_location( f"component_module_{path.stem}", path ) if not spec or not spec.loader: return None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) except (ImportError, AttributeError, OSError, SyntaxError) as e: logger.debug("Could not load module %s: %s", path, e) return None else: return module
__all__ = ["ModuleCache", "ModuleLoader"]