Components reference#

Module summary#

next.components exposes the component discovery, registration, and rendering API. The names in this reference are grouped by their intended audience.

Note

The Application imports, Framework extension, and Internal infrastructure tiers follow the public-surface rules in Which symbols are safe to depend on.

Application imports#

These are the names project code uses day-to-day.

next.components.component#

Registers and looks up context helpers used from component.py.

The component decorator namespace. Inside a component.py use @component.context("key") to publish a value for the component template.

next.components.context#

Mark a function so it fills template variables for this component module.

Pass serialize=True to include the return value in Next.context so JavaScript code on the page can read it via window.Next.context. Pass serializer= to route this key through a custom JsContextSerializer instead of the global JS_CONTEXT_SERIALIZER setting.

The @component.context decorator, bound from ComponentContextManager.context. It registers a context function inside a component.py.

next.components.get_component(name: str, template_path: Path) ComponentInfo | None[source]#

Delegate to components_manager.get_component.

next.components.collect_visible_components(template_path: Path) Mapping[str, ComponentInfo][source]#

Delegate to components_manager.collect_visible_components.

next.components.load_component_template(info: ComponentInfo) str | None[source]#

Return raw template text for info.

next.components.render_component(info: ComponentInfo, context_data: Mapping[str, Any], request: HttpRequest | None = None) str[source]#

Render info to HTML using template context and an optional request.

Manager#

backends is the configured list in consultation order, and reload rebuilds it from the current NEXT_FRAMEWORK. Framework settings changes rebuild the manager on their own, so reload is for a caller that swaps COMPONENT_BACKENDS some other way, which is what next.testing.reset_components does.

class next.components.ComponentsManager[source]#

Loads backends from settings and merges name resolution across them.

__init__() None[source]#

Prepare an empty backend list and load settings on first access.

property template_loader: ComponentTemplateLoader#

Return the shared ComponentTemplateLoader used for template reads.

property component_renderer: ComponentRenderer#

Return the active ComponentRenderer with the configured strategies.

reload() None[source]#

Rebuild the backends from the current NEXT_FRAMEWORK settings.

The render pipeline and the router-walk claims go with the old backends, so the next render resolves against the freshly configured sources.

property backends: tuple[ComponentsBackend, ...]#

Return the configured backends in consultation order.

register_router_walk_folder(folder: Path, pages_root: Path, scope_relative: str) None[source]#

Register components for one folder discovered during a page-tree walk.

The route trail the folder sits on becomes its scope, so the components resolve only for templates under that part of the tree. The folder goes to the first backend whose register_walked_folder claims it.

get_component(name: str, template_path: Path) ComponentInfo | None[source]#

Return the first non-None match from configured backends.

collect_visible_components(template_path: Path) Mapping[str, ComponentInfo][source]#

Merge visible names across backends so the first wins on duplicates.

next.components.components_manager = <next.components.manager.ComponentsManager object>#

Loads backends from settings and merges name resolution across them.

Framework extension#

These names are used when writing a custom component backend or a custom renderer.

Backends#

ComponentsBackend.get_component and ComponentsBackend.collect_visible_components are the two abstract methods every backend implements, and the module-level helpers of the same name above delegate to them through the manager. The rest of the contract has defaults that decline, so a backend implements only what its source can answer. discover is the eager population pass, import_component_modules executes the components’ Python modules, register_walked_folder claims one components folder found during the page-tree walk, and iter_components with global_component_roots lets the system checks enumerate what the backend holds.

class next.components.ComponentsBackend[source]#

Pluggable source of component definitions (files, database, etc.).

abstractmethod get_component(name: str, template_path: Path) ComponentInfo | None[source]#

Return metadata for name from this backend, or None.

abstractmethod collect_visible_components(template_path: Path) Mapping[str, ComponentInfo][source]#

Return a mapping of visible components for template_path.

discover() None[source]#

Populate this backend from its source, once on app ready.

The default does nothing, which suits a backend that resolves names on demand.

import_component_modules() tuple[Path, ...][source]#

Execute the module of every known component and return their paths.

Separate from discover, which only populates the registry, because LAZY_COMPONENT_MODULES leaves those modules unexecuted until a render needs one and a caller reading decorator state cannot wait.

register_walked_folder(folder: Path, pages_root: Path, scope_relative: str) bool[source]#

Register folder under scope_relative below pages_root, or answer False.

The page-tree walk offers each components folder to the backends in configuration order and stops at the first that answers True, so one folder belongs to exactly one backend.

iter_components() Iterable[ComponentInfo][source]#

Return every component this backend has registered, for diagnostics.

The system checks enumerate components through this to report duplicate names and wrong-decorator modules, which the render contract alone cannot answer.

global_component_roots() Iterable[Path][source]#

Return the scope roots whose root-scope components resolve everywhere.

A shared root makes its root-scope components visible from every template, a page tree does not, and the cross-root name check reads this to tell the two apart.

class next.components.FileComponentsBackend(config: dict[str, Any])[source]#

Load components from DIRS and from the filesystem walk in next.urls.

__init__(config: dict[str, Any]) None[source]#

Build registry and scanner from the merged DIRS roots.

COMPONENTS_DIR is not read here. It names the folder the URL router skips inside a page tree, and FileRouterBackend reads it straight from the settings.

discover() None[source]#

Scan every configured component root, once per backend instance.

import_component_modules() tuple[Path, ...][source]#

Import every discovered component.py and return their paths.

The import is deliberately unconditional, which is why a caller that walks decorator state pays under LAZY_COMPONENT_MODULES the import that the lazy mode otherwise avoids.

register_walked_folder(folder: Path, pages_root: Path, scope_relative: str) bool[source]#

Scan the folder into the registry and claim it for this backend.

iter_components() Iterable[ComponentInfo][source]#

Return every discovered component, scanning the roots first.

global_component_roots() Iterable[Path][source]#

Return the DIRS roots whose components resolve from every template.

get_component(name: str, template_path: Path) ComponentInfo | None[source]#

Return the named component visible from template_path.

collect_visible_components(template_path: Path) Mapping[str, ComponentInfo][source]#

Return the full visibility map for template_path.

next.components.register_components_folder_from_router_walk(folder: Path, pages_root: Path, scope_relative: str) None[source]#

Register components for one folder into the live components manager.

The URL router calls this during the page-tree walk and application code does not invoke it directly. It registers into the live components_manager, which claims the folder on first registration, so a repeated walk over the same folder finds nothing left to do. The manager offers the folder to each backend in configuration order and stops at the first whose register_walked_folder answers True. The system checks perform the same registration through ComponentsManager.register_router_walk_folder on the manager they read, so a check run sees every page-tree component without waiting for a router walk and without writing into the live manager.

Context pipeline#

class next.components.ComponentContextManager[source]#

Registers and looks up context helpers used from component.py.

__init__() None[source]#

Create an empty registry for context callables.

context(func_or_key: C, /) C[source]#
context(func_or_key: str | None = None, *, serialize: bool = False, serializer: JsContextSerializer | None = None) Callable[[C], C]

Mark a function so it fills template variables for this component module.

Pass serialize=True to include the return value in Next.context so JavaScript code on the page can read it via window.Next.context. Pass serializer= to route this key through a custom JsContextSerializer instead of the global JS_CONTEXT_SERIALIZER setting.

get_functions(component_path: Path) Sequence[ContextFunction][source]#

Return context callables registered for this component.py path.

class next.components.ComponentContextRegistry[source]#

Maps component.py paths to functions that supply template variables.

__init__() None[source]#

Create an empty path-keyed context-function mapping.

misattributed() tuple[MisattributedContext, ...][source]#

Return every registration bound to a file other than the one running it.

note_misattribution(registered_from: Path, declared_in: Path, func: Callable[..., Any]) None[source]#

Record a @component.context declared outside the running file.

The registration binds to declared_in, which no render of registered_from reads, so the pair feeds the next.E075 diagnostic.

registered_names() dict[Path, tuple[str, ...]][source]#

Return the callable names registered per file, for the diagnostics.

register(component_path: Path, key: str | None, func: Callable[..., Any], *, serialize: bool = False, serializer: JsContextSerializer | None = None) None[source]#

Register func under key for component_path, rejecting reserved keys.

get_functions(component_path: Path) Sequence[ContextFunction][source]#

Return a tuple of registered context functions for component_path.

__len__() int[source]#

Return the total number of registered context functions.

Renderers#

class next.components.ComponentRenderer(strategies: Sequence[ComponentRenderStrategy])[source]#

Picks the first renderer that accepts this component.

__init__(strategies: Sequence[ComponentRenderStrategy]) None[source]#

Bind the renderer to an ordered list of render strategies.

render(info: ComponentInfo, context_data: Mapping[str, Any], request: HttpRequest | None = None) str[source]#

Return HTML from the first matching render strategy.

class next.components.ComponentRenderStrategy(*args, **kwargs)[source]#

Optional render path for a ComponentInfo.

can_render(info: ComponentInfo) bool[source]#

Return True when this strategy handles info.

render(info: ComponentInfo, context_data: Mapping[str, Any], request: HttpRequest | None) str[source]#

Return the rendered HTML for info.

__init__(*args, **kwargs)#
class next.components.SimpleComponentRenderer(template_loader: ComponentTemplateLoader)[source]#

Uses the template string only (no component.py).

__init__(template_loader: ComponentTemplateLoader) None[source]#

Bind this renderer to a shared ComponentTemplateLoader.

can_render(info: ComponentInfo) bool[source]#

Return True for simple components and for missing module files.

render(info: ComponentInfo, context_data: Mapping[str, Any], request: HttpRequest | None) str[source]#

Render info by plain template string rendering.

class next.components.CompositeComponentRenderer(module_loader: ModuleLoader, template_loader: ComponentTemplateLoader)[source]#

Uses render() in component.py when present, otherwise the template.

__init__(module_loader: ModuleLoader, template_loader: ComponentTemplateLoader) None[source]#

Bind the renderer to shared module and template loaders.

can_render(info: ComponentInfo) bool[source]#

Return True for composite components with a loadable component.py.

render(info: ComponentInfo, context_data: Mapping[str, Any], request: HttpRequest | None) str[source]#

Render info via component.py:render or fall back to the template.

class next.components.ComponentTemplateLoader(module_loader: ModuleLoader)[source]#

Read template source from a .djx file or a component module string.

__init__(module_loader: ModuleLoader) None[source]#

Bind this loader to a shared ModuleLoader.

load(info: ComponentInfo) str | None[source]#

Return raw template text for info or None when unavailable.

ComponentsManager wires a single ComponentTemplateLoader into its render pipeline. The loader is fixed and not pluggable, so a custom backend reads component template bodies through this class rather than substituting its own.

Internal infrastructure#

These classes are implementation details. They are exported for testing and advanced instrumentation. Prefer the Application imports tier unless you are building framework tooling.

class next.components.ComponentInfo(name: str, scope_root: Path, scope_relative: str, template_path: Path | None, module_path: Path | None, is_simple: bool)[source]#

What we know about one component after scanning the filesystem.

name: str#
scope_root: Path#
scope_relative: str#
template_path: Path | None#
module_path: Path | None#
is_simple: bool#
resolved_scope_root: Path#
__post_init__() None[source]#

Freeze the resolved form of scope_root once at construction.

__init__(name: str, scope_root: Path, scope_relative: str, template_path: Path | None, module_path: Path | None, is_simple: bool) None#

The duplicate-name check groups by (scope_root, scope_relative, name), the same pair the visibility resolver scores on, so only two components the resolver cannot tell apart collide under next.E020. The same name under a deeper route trail of one tree is the documented override and stays silent.

class next.components.ContextFunction(func: Callable[..., Any], key: str | None, serialize: bool = False, serializer: JsContextSerializer | None = None)[source]#

One function registered to add variables before a component template runs.

The optional serializer overrides the global JS context serializer for the value this callable produces, but only when serialize is true.

func: Callable[..., Any]#
key: str | None#
serialize: bool#
serializer: JsContextSerializer | None#
__init__(func: Callable[..., Any], key: str | None, serialize: bool = False, serializer: JsContextSerializer | None = None) None#
class next.components.ComponentRegistry[source]#

Holds discovered components and whether a directory is a global root.

__init__() None[source]#

Create empty internal lists, indices, and the initial version.

property version: int#

Monotonic counter bumped on every mutation.

register(component: ComponentInfo) None[source]#

Append one component and index it by name.

register_many(components: Iterable[ComponentInfo]) None[source]#

Index every component from the iterable in order.

Follows the Django bulk convention (bulk_create skips per-instance post_save). Receivers that need per-item events should subscribe to components_registered and read the infos tuple. The singular component_registered is not fired from this path.

get_all() Sequence[ComponentInfo][source]#

Return an immutable view of every registered component.

mark_as_root(path: Path) None[source]#

Mark path as globally visible across the tree.

is_root(path: Path) bool[source]#

Return True when path was marked as a global root.

global_roots() frozenset[Path][source]#

Return every path marked as a global root, as an immutable set.

clear() None[source]#

Drop every registered component and reset tracked roots.

__len__() int[source]#

Return the number of registered components.

__contains__(name: str) bool[source]#

Return True when a component with name has been registered.

__iter__() Iterator[ComponentInfo][source]#

Iterate over components in registration order.

class next.components.ComponentVisibilityResolver(registry: ComponentRegistry)[source]#

Decides which component names exist for a given template file path.

__init__(registry: ComponentRegistry) None[source]#

Bind the resolver to a ComponentRegistry and allocate caches.

resolve_visible(template_path: Path) Mapping[str, ComponentInfo][source]#

Return a mapping of visible component names for template_path.

class next.components.ModuleCache(maxsize: int = 128)[source]#

Remembers loaded Python modules by file path and drops the oldest when full.

__init__(maxsize: int = 128) None[source]#

Create an LRU cache with the given capacity.

get(path: Path) ModuleType | object | None[source]#

Return the cached module, a cached None, or the miss sentinel.

set(path: Path, module: ModuleType | None) None[source]#

Store the module (or None on failure) under path, evicting when full.

clear() None[source]#

Drop every entry from the cache.

__len__() int[source]#

Return the number of cached entries.

__contains__(path: Path) bool[source]#

Return True when path currently has a cached entry.

class next.components.ModuleLoader(cache: ModuleCache | None = None)[source]#

Loads a .py file as a module and reuses the last load for the same path.

__init__(cache: ModuleCache | None = None) None[source]#

Bind the loader to a shared or new ModuleCache.

load(path: Path) ModuleType | None[source]#

Return the module for path, loading it on cache miss.

class next.components.ComponentScanner(*, module_loader: ModuleLoader | None = None)[source]#

Scan one folder for .djx files and composite component directories.

__init__(*, module_loader: ModuleLoader | None = None) None[source]#

Wire a module loader for composite component.py files.

scan_directory(directory: Path, scope_root: Path, scope_relative: str) Sequence[ComponentInfo][source]#

Return a list of ComponentInfo found immediately inside directory.

next.components.component_extra_roots_from_config(config: Mapping[str, Any]) list[Path][source]#

Return existing directory paths from the config DIRS entry.

next.components.get_component_paths_for_watch() set[Path][source]#

Return filesystem paths that matter for the dev component reloader.

The scan mutates neither the components manager nor the router registry.

Test doubles#

DummyBackend and BoomBackend are minimal ComponentsBackend implementations kept in this module so that dotted-path resolution in tests works through the standard loader. They are not intended for production use.

DummyBackend accepts a config dict, stores it on self, and resolves no components. Use it to test backend wiring.

class next.components.DummyBackend(config: dict[str, Any])[source]#

Test double that keeps its settings config entry on self.

__init__(config: dict[str, Any]) None[source]#

Keep config on self for assertions about wiring.

get_component(_name: str, _template_path: Path) ComponentInfo | None[source]#

Return None to skip name resolution through this backend.

collect_visible_components(_template_path: Path) Mapping[str, ComponentInfo][source]#

Return an empty mapping because this test double never registers.

BoomBackend raises RuntimeError from __init__ so you can assert that a backend bug reaches the caller instead of being logged as a configuration error.

class next.components.BoomBackend(config: dict[str, Any])[source]#

Test double that raises from __init__ for load error-path tests.

__init__(config: dict[str, Any]) None[source]#

Raise the kind of error the loader never swallows.

get_component(_name: str, _template_path: Path) ComponentInfo | None[source]#

Unreachable because construction always raises.

collect_visible_components(_template_path: Path) Mapping[str, ComponentInfo][source]#

Unreachable because construction always raises.

Signals#

See Signals reference and Signals for the components signals.

The module next.components.signals exposes four django.dispatch.Signal instances.

Signal

Sender

Payload

component_registered

ComponentRegistry

info (ComponentInfo)

components_registered

ComponentRegistry

infos (tuple of ComponentInfo)

component_backend_loaded

The component backend class

instance (ComponentsBackend), config (mapping)

component_rendered

ComponentsManager

info (ComponentInfo), template_path (Path or None)

See also#

See also

Components for the topic guide. Extending for custom backends and render hooks. Testing for rendering components in isolation. Component pipeline for the discovery and render pipeline.