Source code for next.urls.manager

"""Router manager, lazy urlpatterns list, and settings-reload wiring.

`RouterManager` owns the list of active `RouterBackend` instances and
rebuilds it from `NEXT_FRAMEWORK["DEFAULT_PAGE_BACKENDS"]` whenever
framework settings change. `_LazyUrlPatterns` is a `list`-subclass
used as Django's `urlpatterns` so the first access triggers router
and form-action resolution without walking the page tree at import
time.
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, SupportsIndex, overload

from django.urls import clear_url_caches

from next.conf import next_framework_settings
from next.conf.signals import settings_reloaded
from next.forms import form_action_manager

from .backends import RouterBackend, RouterFactory
from .signals import router_reloaded


if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from django.urls import URLPattern, URLResolver


logger = logging.getLogger(__name__)


[docs] class RouterManager: """Load `RouterBackend` instances from `NEXT_FRAMEWORK` and iterate them."""
[docs] def __init__(self) -> None: """Empty backend list until first iteration.""" self._backends: list[RouterBackend] = [] self._config_cache: list[dict[str, Any]] | None = None
[docs] def __repr__(self) -> str: """Debug representation with backend count.""" return f"<{self.__class__.__name__} backends={len(self._backends)}>"
[docs] def __len__(self) -> int: """Return the number of configured backends.""" return len(self._backends)
[docs] def __iter__(self) -> Generator[URLPattern | URLResolver, None, None]: """All patterns from each backend, loading config on first use.""" if not self._backends: self.reload() for backend in self._backends: yield from backend.generate_urls()
[docs] def __getitem__(self, index: int) -> RouterBackend: """Return the backend at the given index.""" return self._backends[index]
[docs] def reload(self) -> None: """Rebuild backends from `DEFAULT_PAGE_BACKENDS` and notify listeners. The Django URL resolver caches resolved patterns. The cache is cleared here so the next request sees the freshly built backend list. The `router_reloaded` signal fires after the rebuild and the cache flush so receivers observe a consistent state. """ self._config_cache = None self._backends.clear() configs = self._get_next_pages_config() for config in configs: try: if backend := RouterFactory.create_backend(config): self._backends.append(backend) except Exception: logger.exception("error creating router from config %s", config) clear_url_caches() router_reloaded.send(sender=type(self))
def _get_next_pages_config(self) -> list[dict[str, Any]]: """Router list from `settings.NEXT_FRAMEWORK` (merged defaults, cached).""" if self._config_cache is not None: return self._config_cache routers = next_framework_settings.DEFAULT_PAGE_BACKENDS if not isinstance(routers, list): self._config_cache = [] return self._config_cache self._config_cache = routers return self._config_cache
router_manager = RouterManager() def _on_settings_reloaded(**_kwargs: object) -> None: """Rebuild router backends when framework settings reload.""" router_manager.reload() settings_reloaded.connect(_on_settings_reloaded) class _LazyUrlPatterns(list): """Defer expanding router and form patterns until first use. Avoids walking the tree at import time. Rebuilds from `router_manager` and `form_action_manager` on each access. Subclasses `list` so `isinstance(urlpatterns, list)` holds. Overrides `__reversed__` because the inherited empty internal buffer would break `reversed(urlpatterns)` in Django's URL resolver. """ def _patterns(self) -> list[URLPattern | URLResolver]: return [ *list(router_manager), *list(form_action_manager), ] def __iter__(self) -> Iterator[URLPattern | URLResolver]: return iter(self._patterns()) def __reversed__(self) -> Iterator[URLPattern | URLResolver]: return reversed(self._patterns()) def __len__(self) -> int: return len(self._patterns()) @overload def __getitem__(self, key: SupportsIndex, /) -> URLPattern | URLResolver: raise NotImplementedError @overload def __getitem__(self, key: slice, /) -> list[URLPattern | URLResolver]: raise NotImplementedError def __getitem__( self, key: SupportsIndex | slice, / ) -> URLPattern | URLResolver | list[URLPattern | URLResolver]: return self._patterns()[key] app_name = "next" urlpatterns = _LazyUrlPatterns() __all__ = [ "RouterManager", "app_name", "router_manager", "urlpatterns", ]