angee.compose.runtime
Runtime composition discovery and generated-tree lifecycle.
Runtime discovers and validates addon composition, then delegates concrete Django model source emission to angee.compose.rendering.ModelRenderer. It owns the complete generated source map and its write, drift, and cleanup lifecycle under runtime/<label>/.
ModelContributions
class ModelContributions(NamedTuple)Abstract source models one addon contributes, split by composition role.
owned
Models emitted as concrete runtime classes under the addon's label.
extensions
Same-row extensions that merge fields into another addon's model.
Runtime
class Runtime()Own composition discovery and the generated runtime tree lifecycle.
One object owns the whole build-time lifecycle while concrete-model planning and rendering stay behind ModelRenderer:
render_sources— the seam: returns{relative path: text}for the whole runtime. Every other entry point renders through it.emit— write that map toruntime_dirduring the explicitangee buildpass (resets, prunes orphans).build— emit stale sources, project the composed folder addons into the host dependency group, then materialize applicable addon-owned migrations onto the downstream Django graph.is_current/check— disk vs the rendered map.reset/clean— delete generated files behind theGENERATED_SENTINELgate while preserving*/migrations/.
Construction groups source models by emitted label, resolves extends extensions, and fails fast on field collisions, so an invalid composition never reaches emission.
__init__
def __init__(addons: Iterable[AppConfig],
*,
runtime_dir: Path,
runtime_module: str = "runtime",
project_dir: Path | None = None) -> NoneCreate a runtime composition for addons and runtime_dir.
from_django
@classmethod
def from_django(cls) -> RuntimeReturn a runtime using installed addons and Django settings.
ANGEE_RUNTIME_DIR is the single owner of where the runtime lives. angee.compose.settings always sets it. A host that installs the composer without it is misconfigured, so fail loudly here rather than let a caller silently skip emission and surface a cryptic missing-model error later in app population.
render_sources
def render_sources() -> dict[Path, str]Return generated runtime source files keyed by relative path.
The composition seam. The returned map (path relative to runtime_dir → file text) is the single source of truth that emit writes and check compares against disk. It contains the generated package __init__ plus, per label, an empty app/migrations __init__ and a models.py, plus (when a consumer addon contributes through a permissions.extends.zed) the merged effective zed under permissions/<package>.zed — see angee.compose.permissions. Migrations themselves are never rendered here — Django's addon materialization and later makemigrations own runtime/<label>/migrations/ (redirected via MIGRATION_MODULES), and cleanup preserves it.
emit
def emit() -> NoneReset the runtime and write all sources (destructive; explicit).
Used by the angee build command: it runs the generated-tree cleanup gate and prunes stale files (e.g. a removed addon's leftover label), then rewrites.
runtime_migrations
def runtime_migrations() -> RuntimeMigrationsReturn the addon migration materializer for this composed runtime.
addon_dependency_group
@property
def addon_dependency_group() -> AddonDependencyGroupReturn the dependency projector for this runtime's composed host.
build
def build() -> AddonDependencyGroupResultEmit stale sources, project addon dependencies, then materialize migrations.
import_generated_models
def import_generated_models() -> NoneImport generated concrete model modules for all emitted labels.
emit_if_stale
def emit_if_stale() -> boolWrite the runtime when it drifts from the sources, on every boot.
Called from the composer's import_models in app-populate phase 2. Write-only and idempotent: it never resets or cleans, so a present-but- stale runtime is healed file by file and a corrupted or non-Angee directory can never abort app population through the destructive cleanup gate. Orphaned files from a removed addon are pruned by the explicit angee build (which calls emit). Returning early when current keeps boots fast and avoids churning files the running process (and Django's autoreloader) already imported.
configure_migration_modules
def configure_migration_modules() -> RuntimeRedirect migrations for emitted runtime app labels.
is_current
def is_current() -> boolReturn whether the on-disk runtime matches the rendered sources.
check
def check() -> NoneRaise for generated-source, dependency-group, or migration drift.
reset
def reset() -> NoneClear generated runtime sources while preserving migrations.
clean
def clean() -> NoneDelete generated runtime files while preserving migrations.
model_contributions
def model_contributions(app_config: AppConfig) -> ModelContributionsReturn source models and extensions declared by one Django app config.
Runtime owns this scan because addons deliberately remain plain Django AppConfig classes with no shared Angee base method to delegate to.