mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
feat: add new agent (#36284)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot]
parent
d1417bbe4b
commit
d9e90d0fa0
@@ -8,6 +8,19 @@ from .account import (
|
||||
TenantAccountRole,
|
||||
TenantStatus,
|
||||
)
|
||||
from .agent import (
|
||||
Agent,
|
||||
AgentConfigRevision,
|
||||
AgentConfigRevisionOperation,
|
||||
AgentConfigSnapshot,
|
||||
AgentIconType,
|
||||
AgentKind,
|
||||
AgentScope,
|
||||
AgentSource,
|
||||
AgentStatus,
|
||||
WorkflowAgentBindingType,
|
||||
WorkflowAgentNodeBinding,
|
||||
)
|
||||
from .api_based_extension import APIBasedExtension, APIBasedExtensionPoint
|
||||
from .comment import (
|
||||
WorkflowComment,
|
||||
@@ -125,6 +138,15 @@ __all__ = [
|
||||
"AccountIntegrate",
|
||||
"AccountStatus",
|
||||
"AccountTrialAppRecord",
|
||||
"Agent",
|
||||
"AgentConfigRevision",
|
||||
"AgentConfigRevisionOperation",
|
||||
"AgentConfigSnapshot",
|
||||
"AgentIconType",
|
||||
"AgentKind",
|
||||
"AgentScope",
|
||||
"AgentSource",
|
||||
"AgentStatus",
|
||||
"ApiRequest",
|
||||
"ApiToken",
|
||||
"ApiToolProvider",
|
||||
@@ -210,6 +232,8 @@ __all__ = [
|
||||
"UploadFile",
|
||||
"Whitelist",
|
||||
"Workflow",
|
||||
"WorkflowAgentBindingType",
|
||||
"WorkflowAgentNodeBinding",
|
||||
"WorkflowAppLog",
|
||||
"WorkflowAppLogCreatedFrom",
|
||||
"WorkflowArchiveLog",
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import DateTime, Index, String, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.uuid_utils import uuidv7
|
||||
|
||||
from .agent_config_entities import AgentSoulConfig, WorkflowNodeJobConfig
|
||||
from .base import Base, DefaultFieldsMixin
|
||||
from .types import EnumText, JSONModelColumn, LongText, StringUUID
|
||||
|
||||
|
||||
class AgentKind(StrEnum):
|
||||
"""Agent implementation family.
|
||||
|
||||
This leaves room for future non-Dify agent implementations while keeping
|
||||
the current roster/workflow APIs scoped to Dify Agent.
|
||||
"""
|
||||
|
||||
# Native Agent backed by the Dify Agent runtime/protocol.
|
||||
DIFY_AGENT = "dify_agent"
|
||||
|
||||
|
||||
class AgentScope(StrEnum):
|
||||
"""Visibility and lifecycle scope of an Agent record."""
|
||||
|
||||
# Workspace-visible Agent that can be reused from Agent Roster.
|
||||
ROSTER = "roster"
|
||||
# Temporary workflow-local Agent created inside one draft workflow node.
|
||||
WORKFLOW_ONLY = "workflow_only"
|
||||
|
||||
|
||||
class AgentSource(StrEnum):
|
||||
"""Origin that created or imported the Agent."""
|
||||
|
||||
# Created from an Agent App composer.
|
||||
AGENT_APP = "agent_app"
|
||||
# Created from a Workflow Agent Composer flow.
|
||||
WORKFLOW = "workflow"
|
||||
# Imported from an external artifact or future CLI/export flow.
|
||||
IMPORTED = "imported"
|
||||
# Created by system bootstrap or managed templates.
|
||||
SYSTEM = "system"
|
||||
|
||||
|
||||
class AgentIconType(StrEnum):
|
||||
"""Supported icon storage formats for Agent roster entries."""
|
||||
|
||||
# ``icon`` stores an uploaded image reference.
|
||||
IMAGE = "image"
|
||||
# ``icon`` stores an emoji character.
|
||||
EMOJI = "emoji"
|
||||
# ``icon`` stores an external image URL.
|
||||
LINK = "link"
|
||||
|
||||
|
||||
class AgentStatus(StrEnum):
|
||||
"""Soft lifecycle state for Agent records."""
|
||||
|
||||
# Available for roster lookup, composer use, and workflow binding.
|
||||
ACTIVE = "active"
|
||||
# Hidden from active roster queries while preserving historical bindings.
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class AgentConfigRevisionOperation(StrEnum):
|
||||
"""Audit operation recorded for Agent Soul version/revision changes."""
|
||||
|
||||
# Initial version creation for a new Agent.
|
||||
CREATE_VERSION = "create_version"
|
||||
# Saves over the user-facing current version by creating a replacement snapshot.
|
||||
SAVE_CURRENT_VERSION = "save_current_version"
|
||||
# Creates a new semantic version for the same Agent.
|
||||
SAVE_NEW_VERSION = "save_new_version"
|
||||
# Saves composer content into a brand-new roster Agent.
|
||||
SAVE_NEW_AGENT = "save_new_agent"
|
||||
# Promotes a workflow-only Agent into the reusable Agent Roster.
|
||||
SAVE_TO_ROSTER = "save_to_roster"
|
||||
|
||||
|
||||
class WorkflowAgentBindingType(StrEnum):
|
||||
"""How a workflow node is bound to an Agent."""
|
||||
|
||||
# Node uses a reusable Agent from the workspace roster.
|
||||
ROSTER_AGENT = "roster_agent"
|
||||
# Node owns a workflow-only Agent that is not visible in the roster.
|
||||
INLINE_AGENT = "inline_agent"
|
||||
|
||||
|
||||
class Agent(DefaultFieldsMixin, Base):
|
||||
"""Workspace-scoped Agent identity used by Agent Roster and workflow-only agents."""
|
||||
|
||||
__tablename__ = "agents"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="agent_pkey"),
|
||||
UniqueConstraint("tenant_id", "roster_unique_name"),
|
||||
Index("agent_tenant_updated_at_idx", "tenant_id", "updated_at"),
|
||||
Index("agent_tenant_scope_idx", "tenant_id", "scope"),
|
||||
Index("agent_tenant_workflow_id_idx", "tenant_id", "workflow_id"),
|
||||
Index("agent_tenant_app_id_idx", "tenant_id", "app_id"),
|
||||
Index("agent_active_config_snapshot_id_idx", "active_config_snapshot_id"),
|
||||
)
|
||||
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str] = mapped_column(LongText, nullable=False, default="")
|
||||
icon_type: Mapped[AgentIconType | None] = mapped_column(EnumText(AgentIconType, length=32), nullable=True)
|
||||
icon: Mapped[str | None] = mapped_column(
|
||||
String(255),
|
||||
nullable=True,
|
||||
comment="Icon payload interpreted by icon_type: emoji character, image file id, or external URL.",
|
||||
)
|
||||
icon_background: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
agent_kind: Mapped[AgentKind] = mapped_column(
|
||||
EnumText(AgentKind, length=32), nullable=False, default=AgentKind.DIFY_AGENT
|
||||
)
|
||||
scope: Mapped[AgentScope] = mapped_column(EnumText(AgentScope, length=32), nullable=False)
|
||||
source: Mapped[AgentSource] = mapped_column(EnumText(AgentSource, length=32), nullable=False)
|
||||
app_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
workflow_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
workflow_node_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
active_config_snapshot_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
status: Mapped[AgentStatus] = mapped_column(
|
||||
EnumText(AgentStatus, length=32), nullable=False, default=AgentStatus.ACTIVE
|
||||
)
|
||||
roster_unique_name: Mapped[str | None] = mapped_column(
|
||||
String(255),
|
||||
sa.Computed("CASE WHEN scope = 'roster' AND status = 'active' THEN name ELSE NULL END"),
|
||||
nullable=True,
|
||||
)
|
||||
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
updated_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
archived_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
archived_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
class AgentConfigSnapshot(DefaultFieldsMixin, Base):
|
||||
"""Immutable Agent Soul snapshot.
|
||||
|
||||
``config_snapshot`` stores ``AgentSoulConfig`` as JSON-backed ``LongText``.
|
||||
It may contain credential or secret references, but must never contain
|
||||
plaintext secrets.
|
||||
"""
|
||||
|
||||
__tablename__ = "agent_config_snapshots"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="agent_config_snapshot_pkey"),
|
||||
UniqueConstraint("agent_id", "version", name="agent_config_snapshot_agent_version_unique"),
|
||||
Index("agent_config_snapshot_tenant_agent_created_at_idx", "tenant_id", "agent_id", "created_at"),
|
||||
Index("agent_config_snapshot_tenant_created_at_idx", "tenant_id", "created_at"),
|
||||
)
|
||||
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
agent_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
version: Mapped[int] = mapped_column(sa.Integer, nullable=False)
|
||||
config_snapshot: Mapped[Any] = mapped_column(JSONModelColumn(AgentSoulConfig), nullable=False)
|
||||
summary: Mapped[str | None] = mapped_column(LongText, nullable=True)
|
||||
version_note: Mapped[str | None] = mapped_column(LongText, nullable=True)
|
||||
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
|
||||
@property
|
||||
def config_snapshot_dict(self) -> dict[str, Any]:
|
||||
if not self.config_snapshot:
|
||||
return {}
|
||||
if hasattr(self.config_snapshot, "model_dump"):
|
||||
return self.config_snapshot.model_dump(mode="json")
|
||||
if isinstance(self.config_snapshot, str):
|
||||
return json.loads(self.config_snapshot)
|
||||
return dict(self.config_snapshot)
|
||||
|
||||
|
||||
class AgentConfigRevision(Base):
|
||||
"""Audit edge for every Agent Soul save operation.
|
||||
|
||||
Revisions link immutable Agent Soul snapshots instead of duplicating the
|
||||
serialized configuration JSON.
|
||||
"""
|
||||
|
||||
__tablename__ = "agent_config_revisions"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="agent_config_revision_pkey"),
|
||||
UniqueConstraint(
|
||||
"agent_id",
|
||||
"revision",
|
||||
name="agent_config_revision_agent_revision_unique",
|
||||
),
|
||||
Index("agent_config_revision_tenant_agent_created_at_idx", "tenant_id", "agent_id", "created_at"),
|
||||
Index(
|
||||
"agent_config_revision_tenant_current_snapshot_created_at_idx",
|
||||
"tenant_id",
|
||||
"current_snapshot_id",
|
||||
"created_at",
|
||||
),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(StringUUID, primary_key=True, default=lambda: str(uuidv7()))
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
agent_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
previous_snapshot_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
current_snapshot_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
revision: Mapped[int] = mapped_column(sa.Integer, nullable=False)
|
||||
operation: Mapped[AgentConfigRevisionOperation] = mapped_column(
|
||||
EnumText(AgentConfigRevisionOperation, length=64), nullable=False
|
||||
)
|
||||
summary: Mapped[str | None] = mapped_column(LongText, nullable=True)
|
||||
version_note: Mapped[str | None] = mapped_column(LongText, nullable=True)
|
||||
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
nullable=False,
|
||||
default=naive_utc_now,
|
||||
server_default=func.current_timestamp(),
|
||||
)
|
||||
|
||||
|
||||
class WorkflowAgentNodeBinding(DefaultFieldsMixin, Base):
|
||||
"""Binding between one workflow node and one Agent config snapshot.
|
||||
|
||||
``node_job_config`` stores Workflow Node Job JSON only. Agent Soul belongs
|
||||
to ``AgentConfigSnapshot.config_snapshot`` and must not be duplicated here.
|
||||
"""
|
||||
|
||||
__tablename__ = "workflow_agent_node_bindings"
|
||||
__table_args__ = (
|
||||
sa.PrimaryKeyConstraint("id", name="workflow_agent_node_binding_pkey"),
|
||||
UniqueConstraint(
|
||||
"tenant_id",
|
||||
"workflow_id",
|
||||
"node_id",
|
||||
name="workflow_agent_node_binding_node_unique",
|
||||
),
|
||||
Index("workflow_agent_node_binding_agent_idx", "tenant_id", "agent_id"),
|
||||
Index("workflow_agent_node_binding_current_snapshot_idx", "tenant_id", "current_snapshot_id"),
|
||||
Index("workflow_agent_node_binding_app_idx", "tenant_id", "app_id"),
|
||||
)
|
||||
|
||||
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
workflow_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||
node_id: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
binding_type: Mapped[WorkflowAgentBindingType] = mapped_column(
|
||||
EnumText(WorkflowAgentBindingType, length=32), nullable=False
|
||||
)
|
||||
agent_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
current_snapshot_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
node_job_config: Mapped[Any] = mapped_column(JSONModelColumn(WorkflowNodeJobConfig), nullable=False)
|
||||
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
updated_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
|
||||
@property
|
||||
def node_job_config_dict(self) -> dict[str, Any]:
|
||||
if not self.node_job_config:
|
||||
return {}
|
||||
if hasattr(self.node_job_config, "model_dump"):
|
||||
return self.node_job_config.model_dump(mode="json")
|
||||
if isinstance(self.node_job_config, str):
|
||||
return json.loads(self.node_job_config)
|
||||
return dict(self.node_job_config)
|
||||
@@ -0,0 +1,136 @@
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
|
||||
class AgentKnowledgeQueryMode(StrEnum):
|
||||
USER_QUERY = "user_query"
|
||||
GENERATED_QUERY = "generated_query"
|
||||
|
||||
|
||||
class WorkflowNodeJobMode(StrEnum):
|
||||
LET_AGENT_FIGURE_IT_OUT = "let_agent_figure_it_out"
|
||||
TELL_AGENT_WHAT_TO_DO = "tell_agent_what_to_do"
|
||||
|
||||
|
||||
class DeclaredOutputType(StrEnum):
|
||||
STRING = "string"
|
||||
NUMBER = "number"
|
||||
OBJECT = "object"
|
||||
ARRAY = "array"
|
||||
BOOLEAN = "boolean"
|
||||
FILE = "file"
|
||||
|
||||
|
||||
class AgentSoulPromptConfig(BaseModel):
|
||||
system_prompt: str = ""
|
||||
|
||||
|
||||
class AgentSoulSkillsFilesConfig(BaseModel):
|
||||
files: list[dict[str, Any]] = Field(default_factory=list)
|
||||
skills: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AgentSoulToolsConfig(BaseModel):
|
||||
dify_tools: list[dict[str, Any]] = Field(default_factory=list)
|
||||
cli_tools: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AgentSoulKnowledgeConfig(BaseModel):
|
||||
datasets: list[dict[str, Any]] = Field(default_factory=list)
|
||||
query_mode: AgentKnowledgeQueryMode | None = None
|
||||
query_config: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentSoulHumanConfig(BaseModel):
|
||||
contacts: list[dict[str, Any]] = Field(default_factory=list)
|
||||
tools: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AgentSoulEnvConfig(BaseModel):
|
||||
variables: list[dict[str, Any]] = Field(default_factory=list)
|
||||
secret_refs: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AgentSoulSandboxConfig(BaseModel):
|
||||
provider: str | None = None
|
||||
config: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentSoulMemoryConfig(BaseModel):
|
||||
scope: str | None = None
|
||||
budget: str | None = None
|
||||
artifacts: list[dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AppVariableConfig(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
type: str = Field(min_length=1, max_length=64)
|
||||
required: bool = False
|
||||
default: Any = None
|
||||
|
||||
|
||||
class AgentSoulConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
schema_version: int = 1
|
||||
prompt: AgentSoulPromptConfig = Field(default_factory=AgentSoulPromptConfig)
|
||||
skills_files: AgentSoulSkillsFilesConfig = Field(default_factory=AgentSoulSkillsFilesConfig)
|
||||
tools: AgentSoulToolsConfig = Field(default_factory=AgentSoulToolsConfig)
|
||||
knowledge: AgentSoulKnowledgeConfig = Field(default_factory=AgentSoulKnowledgeConfig)
|
||||
human: AgentSoulHumanConfig = Field(default_factory=AgentSoulHumanConfig)
|
||||
env: AgentSoulEnvConfig = Field(default_factory=AgentSoulEnvConfig)
|
||||
sandbox: AgentSoulSandboxConfig = Field(default_factory=AgentSoulSandboxConfig)
|
||||
memory: AgentSoulMemoryConfig = Field(default_factory=AgentSoulMemoryConfig)
|
||||
app_features: dict[str, Any] = Field(default_factory=dict)
|
||||
app_variables: list[AppVariableConfig] = Field(default_factory=list)
|
||||
misc_legacy: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class DeclaredOutputFileConfig(BaseModel):
|
||||
extensions: list[str] = Field(default_factory=list)
|
||||
mime_types: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class DeclaredOutputCheckConfig(BaseModel):
|
||||
type: str = Field(min_length=1, max_length=64)
|
||||
prompt: str | None = None
|
||||
benchmark_file_ref: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class DeclaredOutputFailureStrategy(BaseModel):
|
||||
on_type_check_failed: str | None = None
|
||||
on_output_check_failed: str | None = None
|
||||
max_retries: int = Field(default=0, ge=0, le=10)
|
||||
|
||||
|
||||
class DeclaredOutputConfig(BaseModel):
|
||||
id: str | None = None
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
type: DeclaredOutputType
|
||||
description: str | None = None
|
||||
required: bool = True
|
||||
file: DeclaredOutputFileConfig | None = None
|
||||
checks: list[DeclaredOutputCheckConfig] = Field(default_factory=list)
|
||||
failure_strategy: DeclaredOutputFailureStrategy | None = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_file_metadata(self) -> "DeclaredOutputConfig":
|
||||
if self.type == DeclaredOutputType.FILE and self.file is None:
|
||||
self.file = DeclaredOutputFileConfig()
|
||||
if self.type != DeclaredOutputType.FILE and self.file is not None:
|
||||
raise ValueError("file metadata is only allowed for file outputs")
|
||||
return self
|
||||
|
||||
|
||||
class WorkflowNodeJobConfig(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
schema_version: int = 1
|
||||
mode: WorkflowNodeJobMode = WorkflowNodeJobMode.TELL_AGENT_WHAT_TO_DO
|
||||
workflow_prompt: str = ""
|
||||
previous_node_output_refs: list[dict[str, Any]] = Field(default_factory=list)
|
||||
declared_outputs: list[DeclaredOutputConfig] = Field(default_factory=list)
|
||||
human_contacts: list[dict[str, Any]] = Field(default_factory=list)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
@@ -1,8 +1,10 @@
|
||||
import enum
|
||||
import json
|
||||
import uuid
|
||||
from typing import Any, cast
|
||||
|
||||
import sqlalchemy as sa
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import CHAR, TEXT, VARCHAR, LargeBinary, TypeDecorator
|
||||
from sqlalchemy.dialects.mysql import LONGBLOB, LONGTEXT
|
||||
from sqlalchemy.dialects.postgresql import BYTEA, JSONB, UUID
|
||||
@@ -61,6 +63,45 @@ class LongText(TypeDecorator[str | None]):
|
||||
return value
|
||||
|
||||
|
||||
class JSONModelColumn[T: BaseModel](TypeDecorator[T | None]):
|
||||
"""Store a Pydantic model as dialect-adjusted LongText JSON."""
|
||||
|
||||
impl = TEXT
|
||||
cache_ok = True
|
||||
|
||||
_model_class: type[T]
|
||||
|
||||
def __init__(self, model_class: type[T]):
|
||||
if not issubclass(model_class, BaseModel):
|
||||
raise TypeError(f"{model_class.__module__}.{model_class.__name__} must be a Pydantic BaseModel subclass")
|
||||
self._model_class = model_class
|
||||
super().__init__()
|
||||
|
||||
def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
|
||||
if dialect.name == "postgresql":
|
||||
return dialect.type_descriptor(TEXT())
|
||||
elif dialect.name == "mysql":
|
||||
return dialect.type_descriptor(LONGTEXT())
|
||||
else:
|
||||
return dialect.type_descriptor(TEXT())
|
||||
|
||||
def process_bind_param(self, value: T | dict[str, Any] | str | None, dialect: Dialect) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, self._model_class):
|
||||
model = value
|
||||
elif isinstance(value, str):
|
||||
model = self._model_class.model_validate_json(value)
|
||||
else:
|
||||
model = self._model_class.model_validate(value)
|
||||
return json.dumps(model.model_dump(mode="json"), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
def process_result_value(self, value: str | None, dialect: Dialect) -> T | None:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
return self._model_class.model_validate_json(value)
|
||||
|
||||
|
||||
class BinaryData(TypeDecorator[bytes | None]):
|
||||
impl = LargeBinary
|
||||
cache_ok = True
|
||||
|
||||
Reference in New Issue
Block a user