Source code for smarter.apps.llmtool.extension.lifecycle

"""
An extension's lifecycle: what it creates when it is installed, and removes when it is uninstalled.

Most of an extension's lifecycle is declared, and the platform carries it out:

- **settings** (``[[tool.smarter.settings]]``) are configured in the admin console;
- **waffle switches** (``[[tool.smarter.switches]]``) are created as ``ext_<slug>_<name>``
  on install, and deleted on uninstall;
- **SAM resources**, the manifests in the package's ``manifests/`` directory, are applied
  on install and upgrade, and deleted on uninstall. An upgrade deletes the resources that
  the new version no longer ships.

For anything else, an extension names a subclass of :class:`SmarterExtensionLifecycle` in
``[tool.smarter] lifecycle``, and overrides the hooks it needs:

.. code-block:: python

    from smarter.apps.llmtool.extension import LifecycleContext, SmarterExtensionLifecycle

    class Lifecycle(SmarterExtensionLifecycle):
        def post_install(self, context: LifecycleContext) -> None:
            context.apply_manifest(SECRET_MANIFEST)

        def pre_upgrade(self, context: LifecycleContext) -> None:
            if context.previous_version == "1.0.0":
                ...

Hooks run once per cluster, in the process that carries out the administrator's action,
not in every pod. A ``pre_*`` hook that raises cancels the action, and nothing changes. A
``post_*`` hook that raises leaves the extension installed but disabled, with the error
shown in the admin console.
"""

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Optional

from pydantic import BaseModel

from .protocol import ExtensionLogger, extension_logger

if TYPE_CHECKING:
    from smarter.apps.account.models import UserProfile
    from smarter.apps.llmtool.models import Extension


[docs] @dataclass class LifecycleContext: """ What a lifecycle hook knows, and the helpers it may use. :param extension: The extension. :param version: The version being installed, upgraded to, enabled, or uninstalled. :param previous_version: On an upgrade, the version being replaced; otherwise None. :param settings: The extension's settings, as its tools see them. :param user_profile: The superuser who is carrying out the action, who owns the resources it creates. """ extension: "Extension" version: str settings: BaseModel user_profile: "UserProfile" previous_version: Optional[str] = None logger: ExtensionLogger = field(default=None) # type: ignore[assignment] def __post_init__(self): if self.logger is None: self.logger = extension_logger(self.extension.slug) @property def slug(self) -> str: """The extension's slug.""" return self.extension.slug
[docs] def apply_manifest(self, manifest: str) -> tuple[str, str]: """ Apply a SAM manifest, owned by :attr:`user_profile`, and record it as created by this extension. The resource is deleted when the extension is uninstalled. :param manifest: The manifest, as YAML or JSON. :returns: The resource's kind and name. :raises SmarterExtensionInstallError: If the manifest cannot be applied. """ # pylint: disable=import-outside-toplevel from .resources import apply_extension_manifest return apply_extension_manifest(self.extension, manifest, self.user_profile, self.version, managed=False)
[docs] def delete_resource(self, kind: str, name: str) -> None: """ Delete a SAM resource that this extension created. :param kind: The resource's kind, e.g. ``Secret``. :param name: The resource's name. """ # pylint: disable=import-outside-toplevel from .resources import delete_extension_resource delete_extension_resource(self.extension, kind, name, self.user_profile)
[docs] class SmarterExtensionLifecycle: """ The base class of an extension's lifecycle hooks. Every hook does nothing unless overridden. A new instance is created for each action. """
[docs] def pre_install(self, context: LifecycleContext) -> None: """Run before an extension is first installed. Raise to cancel the install. """
[docs] def post_install(self, context: LifecycleContext) -> None: """Run after an extension is first installed, its switches created and its manifests applied."""
[docs] def pre_upgrade(self, context: LifecycleContext) -> None: """Run, from the new version's code, before it replaces :attr:`LifecycleContext.previous_version`. Raise to cancel. """
[docs] def post_upgrade(self, context: LifecycleContext) -> None: """Run, from the new version's code, after it replaced :attr:`LifecycleContext.previous_version`."""
[docs] def pre_uninstall(self, context: LifecycleContext) -> None: """Run before an extension is uninstalled. Raise to cancel the uninstall. """
[docs] def post_uninstall(self, context: LifecycleContext) -> None: """Run after an extension's resources and switches are deleted, just before its rows are."""
[docs] def on_enable(self, context: LifecycleContext) -> None: """Run when an administrator enables the extension. Raise to keep it disabled. """
[docs] def on_disable(self, context: LifecycleContext) -> None: """Run when an administrator disables the extension."""
__all__ = ["LifecycleContext", "SmarterExtensionLifecycle"]