Source code for smarter.apps.plugin.contract.models
"""
Pydantic models of the plugin data that the REST api's responses include.
:class:`PluginApiPluginMeta` is a plugin's metadata, as the REST api returns it, e.g. in an
LLMClient's plugins. It is the output of :class:`smarter.apps.plugin.serializers.PluginMetaSerializer`,
which the REST api returned before it was modeled, and which clients already read: the owner is
nested, rather than its id.
The models forbid extra fields: the api returns exactly what they declare.
"""
from datetime import datetime
from typing import TYPE_CHECKING, Literal, Optional
from pydantic import Field
from smarter.apps.account.contract.models import (
AccountApiModel,
AccountApiUserProfile,
MetaDataApiAnnotations,
)
from smarter.apps.account.serializers import user_can_delete
if TYPE_CHECKING:
from smarter.apps.plugin.models import PluginMeta
PluginApiPluginClass = Literal["api", "skill", "sql", "static", "websearch", "imagesearch"]
"""A plugin's class: the choices of ``PluginMeta.plugin_class``.
A test fails if the two differ.
"""
[docs]
class PluginApiPluginMeta(AccountApiModel):
"""A plugin's metadata: a :class:`smarter.apps.plugin.models.PluginMeta`."""
id: int = Field(..., description="The row's primary key.")
created_at: Optional[datetime] = Field(None, description="TimestampedModel.created_at.")
updated_at: Optional[datetime] = Field(None, description="TimestampedModel.updated_at.")
name: str = Field(..., description="MetaDataModel.name, in snake_case.")
user_profile: AccountApiUserProfile = Field(..., description="The plugin's owner.")
description: Optional[str] = Field(None, description="MetaDataModel.description.")
plugin_class: PluginApiPluginClass = Field(..., description="PluginMeta.plugin_class.")
version: Optional[str] = Field(None, description="MetaDataModel.version, e.g. 1.0.0.")
annotations: MetaDataApiAnnotations = Field(None, description="MetaDataModel.annotations.")
tags: list[str] = Field(default_factory=list, description="MetaDataModel.tags, by name.")
manifest_url: str = Field(..., description="The url of the plugin's manifest in the web console.")
ready: bool = Field(..., description="Whether the plugin has the data and configuration that it needs.")
kind: str = Field(..., description="The plugin's SAM kind, e.g. SqlPlugin.")
can_delete: bool = Field(
...,
description="Whether the requesting user may delete the plugin now: they own it, and nothing depends on it.",
)
[docs]
@classmethod
def from_model(cls, plugin_meta: "PluginMeta", user: object = None) -> "PluginApiPluginMeta":
"""
The contract's model of a plugin's metadata.
:param plugin_meta: The row, whose ``user_profile`` and tags should be fetched with it.
:type plugin_meta: smarter.apps.plugin.models.PluginMeta
:param user: The requesting user, for ``can_delete``. None may delete nothing.
:type user: object
:returns: The model.
:rtype: PluginApiPluginMeta
"""
kind = plugin_meta.kind.value
return cls(
id=plugin_meta.pk,
created_at=plugin_meta.created_at,
updated_at=plugin_meta.updated_at,
name=plugin_meta.name,
user_profile=AccountApiUserProfile.from_model(plugin_meta.user_profile),
description=plugin_meta.description,
plugin_class=plugin_meta.plugin_class, # type: ignore[arg-type]
version=plugin_meta.version,
annotations=plugin_meta.annotations,
tags=[tag.name for tag in plugin_meta.tags.all()],
manifest_url=plugin_meta.manifest_url,
ready=plugin_meta.ready,
kind=kind,
can_delete=user_can_delete(plugin_meta, kind, user),
)
__all__ = ["PluginApiPluginClass", "PluginApiPluginMeta"]