refactor(api): migrate snippet workspace endpoints to BaseModel (#37956)

Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
This commit is contained in:
chariri 2026-07-09 13:21:27 +09:00 committed by GitHub
parent 3cd8d850fa
commit 9bb3b1fa98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 28 additions and 99 deletions

View File

@ -148,6 +148,8 @@ class PublishWorkflowPayload(BaseModel):
"""Payload for publishing snippet workflow."""
knowledge_base_setting: dict[str, Any] | None = Field(default=None)
marked_name: str | None = Field(default=None, max_length=20)
marked_comment: str | None = Field(default=None, max_length=100)
class SnippetImportPayload(BaseModel):

View File

@ -1,12 +1,9 @@
import logging
from datetime import datetime
from typing import Any
from urllib.parse import quote
from uuid import UUID
from flask import Response, request
from flask_restx import Resource
from pydantic import Field as PydanticField
from pydantic import field_validator
from sqlalchemy.orm import Session, sessionmaker
from werkzeug.exceptions import NotFound
@ -37,7 +34,8 @@ from controllers.console.wraps import (
from core.plugin.entities.plugin import PluginDependency
from extensions.ext_database import db
from fields.base import ResponseModel
from libs.helper import dump_response, to_timestamp
from fields.snippet_fields import SnippetListItemResponse, SnippetPaginationResponse, SnippetResponse
from libs.helper import dump_response
from libs.login import login_required
from models import Account
from models.snippet import SnippetType
@ -65,77 +63,6 @@ class SnippetUseCountResponse(ResponseModel):
use_count: int
class SnippetTagResponse(ResponseModel):
id: str
name: str
type: str
class SnippetAccountResponse(ResponseModel):
id: str
name: str
email: str
class SnippetListItemResponse(ResponseModel):
id: str
name: str
description: str | None
type: SnippetType
version: int
use_count: int
is_published: bool
icon_info: dict[str, Any] | None
tags: list[SnippetTagResponse]
created_by: str | None
author_name: str | None
created_at: int
updated_by: str | None
updated_at: int
@field_validator("created_at", "updated_at", mode="before")
@classmethod
def _normalize_timestamp(cls, value: datetime | int | None) -> int:
timestamp = to_timestamp(value)
if timestamp is None:
raise ValueError("timestamp is required")
return timestamp
class SnippetResponse(ResponseModel):
id: str
name: str
description: str | None
type: SnippetType
version: int
use_count: int
is_published: bool
icon_info: dict[str, Any] | None
graph: dict[str, Any] = PydanticField(validation_alias="graph_dict")
input_fields: list[dict[str, Any]] = PydanticField(validation_alias="input_fields_list")
tags: list[SnippetTagResponse]
created_by: SnippetAccountResponse | None = PydanticField(validation_alias="created_by_account")
created_at: int
updated_by: SnippetAccountResponse | None = PydanticField(validation_alias="updated_by_account")
updated_at: int
@field_validator("created_at", "updated_at", mode="before")
@classmethod
def _normalize_timestamp(cls, value: datetime | int | None) -> int:
timestamp = to_timestamp(value)
if timestamp is None:
raise ValueError("timestamp is required")
return timestamp
class SnippetPaginationResponse(ResponseModel):
data: list[SnippetListItemResponse]
page: int
limit: int
total: int
has_more: bool
def _snippet_service() -> SnippetService:
return SnippetService(sessionmaker(bind=db.engine, expire_on_commit=False))
@ -259,7 +186,7 @@ class CustomizedSnippetDetailApi(Resource):
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, snippet_id: str):
def get(self, current_tenant_id: str, snippet_id: UUID):
"""Get customized snippet details."""
snippet_service = _snippet_service()
snippet = snippet_service.get_snippet_by_id(
@ -535,4 +462,4 @@ class CustomizedSnippetUseCountIncrementApi(Resource):
session.commit()
session.refresh(snippet)
return SnippetUseCountResponse(result="success", use_count=snippet.use_count).model_dump(mode="json"), 200
return {"result": "success", "use_count": snippet.use_count}, 200

View File

@ -20493,6 +20493,8 @@ Payload for publishing snippet workflow.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| knowledge_base_setting | object | | No |
| marked_comment | string | | No |
| marked_name | string | | No |
#### PublishedWorkflowRunPayload
@ -21270,14 +21272,6 @@ Validated metadata extracted from a Skill package.
| inferable | boolean | | Yes |
| reason | string | | No |
#### SnippetAccountResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| email | string | | Yes |
| id | string | | Yes |
| name | string | | Yes |
#### SnippetDependencyCheckResponse
| Name | Type | Description | Required |
@ -21407,7 +21401,7 @@ Payload for running a loop node in snippet draft workflow.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| created_at | integer | | Yes |
| created_by | [SnippetAccountResponse](#snippetaccountresponse) | | Yes |
| created_by | [SimpleAccountResponse](#simpleaccountresponse) | | Yes |
| description | string | | Yes |
| graph | object | | Yes |
| icon_info | object | | Yes |
@ -21418,7 +21412,7 @@ Payload for running a loop node in snippet draft workflow.
| tags | [ [SnippetTagResponse](#snippettagresponse) ] | | Yes |
| type | [SnippetType](#snippettype) | | Yes |
| updated_at | integer | | Yes |
| updated_by | [SnippetAccountResponse](#snippetaccountresponse) | | Yes |
| updated_by | [SimpleAccountResponse](#simpleaccountresponse) | | Yes |
| use_count | integer | | Yes |
| version | integer | | Yes |

View File

@ -1,6 +1,6 @@
import json
import logging
from collections.abc import Iterator, Mapping, Sequence
from collections.abc import Generator, Mapping, Sequence
from contextlib import contextmanager
from datetime import UTC, datetime
from typing import Any
@ -68,7 +68,7 @@ class SnippetService:
self._workflow_run_repo = DifyAPIRepositoryFactory.create_api_workflow_run_repository(session_maker)
@contextmanager
def _session_scope(self) -> Iterator[Session]:
def _session_scope(self) -> Generator[Session, None, None]:
current_session = getattr(self, "_session", None)
if current_session is not None:
yield current_session
@ -625,8 +625,6 @@ class SnippetService:
conversation_variables=[],
rag_pipeline_variables=draft_workflow.rag_pipeline_variables,
kind=WorkflowKind.SNIPPET.value,
marked_name="",
marked_comment="",
)
session.add(workflow)

View File

@ -1240,6 +1240,8 @@ export type PublishWorkflowPayload = {
knowledge_base_setting?: {
[key: string]: unknown
} | null
marked_comment?: string | null
marked_name?: string | null
}
export type WorkflowPublishResponse = {

View File

@ -767,6 +767,8 @@ export const zWorkflowDraftVariableUpdatePayload = z.object({
*/
export const zPublishWorkflowPayload = z.object({
knowledge_base_setting: z.record(z.string(), z.unknown()).nullish(),
marked_comment: z.string().max(100).nullish(),
marked_name: z.string().max(20).nullish(),
})
/**

View File

@ -201,6 +201,8 @@ export type PublishWorkflowPayload = {
knowledge_base_setting?: {
[key: string]: unknown
} | null
marked_comment?: string | null
marked_name?: string | null
}
export type WorkflowPublishResponse = {

View File

@ -123,6 +123,8 @@ export const zWorkflowDraftVariableUpdatePayload = z.object({
*/
export const zPublishWorkflowPayload = z.object({
knowledge_base_setting: z.record(z.string(), z.unknown()).nullish(),
marked_comment: z.string().max(100).nullish(),
marked_name: z.string().max(20).nullish(),
})
/**

View File

@ -52,7 +52,7 @@ export type CreateSnippetPayload = {
export type SnippetResponse = {
created_at: number
created_by: SnippetAccountResponse | null
created_by: SimpleAccountResponse | null
description: string | null
graph: {
[key: string]: unknown
@ -69,7 +69,7 @@ export type SnippetResponse = {
tags: Array<SnippetTagResponse>
type: SnippetType
updated_at: number
updated_by: SnippetAccountResponse | null
updated_by: SimpleAccountResponse | null
use_count: number
version: number
}
@ -1060,7 +1060,7 @@ export type InputFieldDefinition = {
type?: string | null
}
export type SnippetAccountResponse = {
export type SimpleAccountResponse = {
email: string
id: string
name: string

View File

@ -697,9 +697,9 @@ export const zCreateSnippetPayload = z.object({
})
/**
* SnippetAccountResponse
* SimpleAccountResponse
*/
export const zSnippetAccountResponse = z.object({
export const zSimpleAccountResponse = z.object({
email: z.string(),
id: z.string(),
name: z.string(),
@ -726,7 +726,7 @@ export const zSnippetType = z.enum(['group', 'node'])
*/
export const zSnippetResponse = z.object({
created_at: z.int(),
created_by: zSnippetAccountResponse.nullable(),
created_by: zSimpleAccountResponse.nullable(),
description: z.string().nullable(),
graph: z.record(z.string(), z.unknown()),
icon_info: z.record(z.string(), z.unknown()).nullable(),
@ -737,7 +737,7 @@ export const zSnippetResponse = z.object({
tags: z.array(zSnippetTagResponse),
type: zSnippetType,
updated_at: z.int(),
updated_by: zSnippetAccountResponse.nullable(),
updated_by: zSimpleAccountResponse.nullable(),
use_count: z.int(),
version: z.int(),
})