Skip to content

angee.platform.installer

The AddonInstaller seam — the one writer of settings.yaml's INSTALLED_APPS.

Installing an addon = adding its root to settings.yaml INSTALLED_APPS and rebuilding; uninstalling = removing it. settings.yaml stays the boot source (no DB-driven settings-load): this module owns the edit of that one file, and the next compose reads it.

The split mirrors an ImplClassField registry (VcsBridge.backend_class / ANGEE_VCS_BACKEND_CLASSES), in its row-less variant — there is no per-row choice, it is a per-deployment one, so the selection is a settings key resolved against a registry rather than a model column:

  • :class:AddonInstaller owns all YAML logic — the comment-preserving ruamel.yaml round-trip read → edit INSTALLED_APPS → write → request rebuild.
  • :class:AddonInstallerBackend is pure transport — it only moves the settings bytes and asks for a rebuild. local (the dev stub, defined here) edits the local settings.yaml and treats rebuild as a pending no-op (the addon composes on the next angee dev boot). The production operator backend — edit + rebuild over the operator daemon — is contributed by the platform_integrate_operator bridge addon, so platform stays unaware of the operator (they are siblings).

The backend is chosen by settings.ANGEE_ADDON_INSTALLER_BACKEND against the settings.ANGEE_ADDON_INSTALLER_BACKEND_CLASSES key→dotted-path registry. This addon's autoconfig supplies the default (local) and the local entry; the platform_integrate_operator bridge contributes the operator entry, and a deployment flips the key to operator. :func:register_checks binds a manage.py check guard over that registry, the row-less analogue of ImplClassField.check.

AddonInstallerBackend

python
class AddonInstallerBackend()

Pure transport for the settings.yaml that lists INSTALLED_APPS + the rebuild.

The :class:AddonInstaller owns all YAML logic; a backend only moves the settings bytes and asks for a rebuild. Subclasses register a short :attr:key selected by settings.ANGEE_ADDON_INSTALLER_BACKEND.

read_settings_text

python
def read_settings_text() -> str

Return the current settings.yaml text (FileNotFoundError if absent).

write_settings_text

python
def write_settings_text(text: str) -> None

Write the edited settings.yaml text back to its source.

request_rebuild

python
def request_rebuild() -> str

Trigger a rebuild/restart and return a short status marker.

LocalInstallerBackend

python
class LocalInstallerBackend(AddonInstallerBackend)

Dev stub: edit the local settings.yaml beside manage.py; rebuild is pending.

Reads/writes settings.yaml under settings.BASE_DIR and treats the rebuild as a no-op pending marker — the edited addon composes on the next angee dev boot. Makes the whole install/uninstall flow work and testable in dev.

read_settings_text

python
def read_settings_text() -> str

Return the local settings.yaml text (FileNotFoundError if absent).

write_settings_text

python
def write_settings_text(text: str) -> None

Write the edited text atomically, the way the composer writes generated files.

request_rebuild

python
def request_rebuild() -> str

Mark the rebuild pending — the addon composes on the next angee dev boot.

InstallResult

python
@dataclass(frozen=True, slots=True)
class InstallResult()

The outcome of one install/uninstall edit, mapped by a resolver to a report.

already is True when the edit was a no-op — the root was already present on install, or already absent on uninstall. refused is a non-empty reason when the edit was not applied — a forced (depended-on) addon, an addon that is not materialised, or a deployment whose settings.yaml cannot be edited — and is then the only not-:attr:ok outcome.

refusal

python
@classmethod
def refusal(cls, name: str, action: str, reason: str) -> InstallResult

Return a not-applied result whose :attr:summary is reason.

ok

python
@property
def ok() -> bool

Return whether the edit was applied (a refusal is the only not-ok outcome).

summary

python
@property
def summary() -> str

Return the operator-facing one-line summary of this edit (the console message).

The outcome owns its own human description — the install/uninstall resolvers only relay it — so the wording stays in one place across surfaces. A refusal relays its caller-supplied reason (the policy/availability owner authors it).

AddonInstaller

python
class AddonInstaller()

Owns the comment-preserving settings.yaml INSTALLED_APPS edit.

A backend supplies the bytes and the rebuild; this class does the one thing that must preserve operator comments and key order — the ruamel.yaml round-trip edit of the INSTALLED_APPS sequence in place (append on install, remove on uninstall). Author order is preserved; the composer sorts the dependency closure deterministically at boot regardless.

__init__

python
def __init__(backend: AddonInstallerBackend) -> None

Bind the transport backend and a round-trip YAML editor.

ruamel's round-trip mode does not auto-detect a file's block-sequence indentation and defaults best_width to ~80, so an unconfigured editor reflows every sequence to flush-left and wraps long scalars — defeating the comment/layout preservation ruamel exists for here. Pin the project's - item style (2-space mapping, dash at offset 2 within a 4-space sequence indent) and a wide width so an edit stays byte-faithful outside the one changed line.

installed_app_names

python
def installed_app_names() -> tuple[str, ...]

Return the desired INSTALLED_APPS roots from settings.yaml.

Best-effort: returns () when the file is absent/unreadable (bare test settings, or the operator backend not active), so the reconcile that reads this for the pending diff never raises.

install

python
def install(name: str) -> InstallResult

Add name to INSTALLED_APPS (idempotent), then request a rebuild.

Degrades to a clear refusal when the backend cannot reach an editable settings.yaml (no file, or the operator transport is not built yet) so the edge reports it rather than surfacing a raw transport error.

uninstall

python
def uninstall(name: str) -> InstallResult

Remove name from INSTALLED_APPS (no-op when absent), then request a rebuild.

Degrades to a clear refusal when the backend cannot reach an editable settings.yaml (see :meth:install).

addon_installer

python
def addon_installer() -> AddonInstaller

Return the configured :class:AddonInstaller.

Resolves settings.ANGEE_ADDON_INSTALLER_BACKEND against the settings.ANGEE_ADDON_INSTALLER_BACKEND_CLASSES registry through the shared :func:~angee.base.registry.resolve_impl_class owner — the row-less form of ImplClassField.resolve_class (trusted settings path, never row text, with the AddonInstallerBackend subclass check).

register_checks

python
def register_checks() -> None

Register the installer-backend system check (called from PlatformConfig.ready).

Released under the AGPL-3.0 License.