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:
AddonInstallerowns all YAML logic — the comment-preservingruamel.yamlround-trip read → editINSTALLED_APPS→ write → request rebuild. - :class:
AddonInstallerBackendis pure transport — it only moves the settings bytes and asks for a rebuild.local(the dev stub, defined here) edits the localsettings.yamland treats rebuild as a pending no-op (the addon composes on the nextangee devboot). The productionoperatorbackend — edit + rebuild over the operator daemon — is contributed by theplatform_integrate_operatorbridge addon, soplatformstays 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
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
def read_settings_text() -> strReturn the current settings.yaml text (FileNotFoundError if absent).
write_settings_text
def write_settings_text(text: str) -> NoneWrite the edited settings.yaml text back to its source.
request_rebuild
def request_rebuild() -> strTrigger a rebuild/restart and return a short status marker.
LocalInstallerBackend
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
def read_settings_text() -> strReturn the local settings.yaml text (FileNotFoundError if absent).
write_settings_text
def write_settings_text(text: str) -> NoneWrite the edited text atomically, the way the composer writes generated files.
request_rebuild
def request_rebuild() -> strMark the rebuild pending — the addon composes on the next angee dev boot.
InstallResult
@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
@classmethod
def refusal(cls, name: str, action: str, reason: str) -> InstallResultReturn a not-applied result whose :attr:summary is reason.
ok
@property
def ok() -> boolReturn whether the edit was applied (a refusal is the only not-ok outcome).
summary
@property
def summary() -> strReturn 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
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__
def __init__(backend: AddonInstallerBackend) -> NoneBind 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
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
def install(name: str) -> InstallResultAdd 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
def uninstall(name: str) -> InstallResultRemove 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
def addon_installer() -> AddonInstallerReturn 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
def register_checks() -> NoneRegister the installer-backend system check (called from PlatformConfig.ready).